From e3a3ecf1a548845ed490e0846afeaadfd9f1e4a4 Mon Sep 17 00:00:00 2001 From: Rico Suter Date: Tue, 2 May 2017 17:43:09 +0200 Subject: [PATCH 01/19] Added DotLiquid library --- .../NJsonSchema.CodeGeneration.CSharp.csproj | 3 + .../Templates/Class.liquid | 89 +++++++++++++++++++ .../DefaultTemplateFactory.cs | 27 ++++-- .../LiquidTemplate.cs | 31 +++++++ .../NJsonSchema.CodeGeneration.csproj | 1 + 5 files changed, 144 insertions(+), 7 deletions(-) create mode 100644 src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.liquid create mode 100644 src/NJsonSchema.CodeGeneration/LiquidTemplate.cs diff --git a/src/NJsonSchema.CodeGeneration.CSharp/NJsonSchema.CodeGeneration.CSharp.csproj b/src/NJsonSchema.CodeGeneration.CSharp/NJsonSchema.CodeGeneration.CSharp.csproj index 404216002..b5f95e6e5 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/NJsonSchema.CodeGeneration.CSharp.csproj +++ b/src/NJsonSchema.CodeGeneration.CSharp/NJsonSchema.CodeGeneration.CSharp.csproj @@ -13,6 +13,9 @@ True + + + diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.liquid b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.liquid new file mode 100644 index 000000000..95569fc66 --- /dev/null +++ b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.liquid @@ -0,0 +1,89 @@ +{% if Model.HasDescription %} +/// <#=ConversionUtilities.ConvertCSharpDocBreaks(Model.Description, 0)#> +{% endif %} +{% if Model.HasDiscriminator %} +[Newtonsoft.Json.JsonConverter(typeof(JsonInheritanceConverter), "{{ Model.Discriminator }}")] +{% endif %} +[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "{{ JsonSchema4.ToolchainVersion }}")] +public partial class {{Model.Class}} {{Model.Inheritance}} +{ +{% if Model.Inpc -%} +{% for property in Model.Properties %} + private {{property.Type}} {{property.FieldName}}{% if property.HasDefaultValue %} = {{property.DefaultValue}}{% endif -%}; +{% endfor %} + +{% endif %} +<#foreach(var property in Model.Properties){#> +<# if(property.HasDescription){#> + /// <#=ConversionUtilities.ConvertCSharpDocBreaks(property.Description, 1)#> +<# }#> + [Newtonsoft.Json.JsonProperty("<#=property.Name#>", Required = <#=property.JsonPropertyRequired#><#if(property.IsStringEnumArray){#>, ItemConverterType = typeof(Newtonsoft.Json.Converters.StringEnumConverter)<#}#>)] +<# if(property.RenderRequiredAttribute){#> + [System.ComponentModel.DataAnnotations.Required] +<# }#> +<# if(property.RenderRangeAttribute){#> + [System.ComponentModel.DataAnnotations.Range(<#=property.RangeMinimumValue #>, <#=property.RangeMaximumValue #>)] +<# }#> +<# if(property.RenderStringLengthAttribute){#> + [System.ComponentModel.DataAnnotations.StringLength(<#=property.StringLengthMaximumValue #><# if(property.StringLengthMinimumValue > 0){ #>, MinimumLength = <#=property.StringLengthMinimumValue #><# } #>)] +<# }#> +<# if(property.RenderRegularExpressionAttribute){#> + [System.ComponentModel.DataAnnotations.RegularExpression(@"<#=property.RegularExpressionValue#>")] +<# }#> +<#if(property.IsStringEnum){#> + [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] +<# }#> + public <#=property.Type#> <#=property.PropertyName#><# if(!Model.Inpc){#> { get; <#if(property.HasSetter){#>set; <#}#>}<#if(property.HasDefaultValue){#> = <#=property.DefaultValue#>;<#}#> + +<# }else{#> + + { + get { return <#=property.FieldName#>; } +<#if(property.HasSetter){#> + set + { + if (<#=property.FieldName#> != value) + { + <#=property.FieldName#> = value; + RaisePropertyChanged(); + } + } +<#}#> + } +<#}#> + +<#}#> +<#if(Model.HasAdditionalPropertiesType){#> + private System.Collections.Generic.IDictionary> _additionalProperties = new System.Collections.Generic.Dictionary>(); + + [Newtonsoft.Json.JsonExtensionData] + public System.Collections.Generic.IDictionary> AdditionalProperties + { + get { return _additionalProperties; } + set { _additionalProperties = value; } + } + +<#}#> +<#if(Model.Inpc){#> + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + +<#}#> + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this<#=Model.JsonSerializerParameterCode#>); + } + + public static <#=Model.Class#> FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject<<#=Model.Class#>>(data<#=Model.JsonSerializerParameterCode#>); + } +<#if(Model.Inpc){#> + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } +<#}#> +} \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration/DefaultTemplateFactory.cs b/src/NJsonSchema.CodeGeneration/DefaultTemplateFactory.cs index d7fe8129f..28e6f8f3c 100644 --- a/src/NJsonSchema.CodeGeneration/DefaultTemplateFactory.cs +++ b/src/NJsonSchema.CodeGeneration/DefaultTemplateFactory.cs @@ -7,7 +7,9 @@ //----------------------------------------------------------------------- using System; +using System.IO; using System.Reflection; +using DotLiquid; namespace NJsonSchema.CodeGeneration { @@ -23,15 +25,26 @@ public class DefaultTemplateFactory : ITemplateFactory /// Could not load template.. public virtual ITemplate CreateTemplate(string package, string template, object model) { - var typeName = "NJsonSchema.CodeGeneration." + package + ".Templates." + template + "Template"; - var type = Type.GetType(typeName); - if (type == null) - type = Assembly.Load(new AssemblyName("NJsonSchema.CodeGeneration." + package))?.GetType(typeName); + var assembly = Assembly.Load(new AssemblyName("NJsonSchema.CodeGeneration." + package)); + var resourceName = "NJsonSchema.CodeGeneration." + package + ".Templates." + template + ".liquid"; + var resource = assembly.GetManifestResourceStream(resourceName); + if (resource != null) + { + using (var reader = new StreamReader(resource)) + return new LiquidTemplate(reader.ReadToEnd(), model); + } + else + { + var typeName = "NJsonSchema.CodeGeneration." + package + ".Templates." + template + "Template"; + var type = Type.GetType(typeName); + if (type == null) + type = Assembly.Load(new AssemblyName("NJsonSchema.CodeGeneration." + package))?.GetType(typeName); - if (type != null) - return (ITemplate)Activator.CreateInstance(type, model); + if (type != null) + return (ITemplate)Activator.CreateInstance(type, model); - throw new InvalidOperationException("Could not load template '" + template + "'."); + throw new InvalidOperationException("Could not load template '" + template + "'."); + } } } } \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration/LiquidTemplate.cs b/src/NJsonSchema.CodeGeneration/LiquidTemplate.cs new file mode 100644 index 000000000..64bff97a7 --- /dev/null +++ b/src/NJsonSchema.CodeGeneration/LiquidTemplate.cs @@ -0,0 +1,31 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Rico Suter. All rights reserved. +// +// https://github.com/rsuter/NJsonSchema/blob/master/LICENSE.md +// Rico Suter, mail@rsuter.com +//----------------------------------------------------------------------- + +using DotLiquid; + +namespace NJsonSchema.CodeGeneration +{ + internal class LiquidTemplate : ITemplate + { + private readonly string _template; + private readonly object _model; + + public LiquidTemplate(string template, object model) + { + _template = template; + _model = model; + } + + public string Render() + { + var tpl = Template.Parse(_template); + var hash = Hash.FromAnonymousObject(new { Model = Hash.FromAnonymousObject(_model) }); + return tpl.Render(hash); + } + } +} \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration/NJsonSchema.CodeGeneration.csproj b/src/NJsonSchema.CodeGeneration/NJsonSchema.CodeGeneration.csproj index 76185b37d..15b344a63 100644 --- a/src/NJsonSchema.CodeGeneration/NJsonSchema.CodeGeneration.csproj +++ b/src/NJsonSchema.CodeGeneration/NJsonSchema.CodeGeneration.csproj @@ -14,6 +14,7 @@ True + From c51ca8e264a86ecd6679bc28954972316d17a1d0 Mon Sep 17 00:00:00 2001 From: Rico Suter Date: Tue, 16 May 2017 11:49:05 +0200 Subject: [PATCH 02/19] Added class liquid template --- .../CSharpGenerator.cs | 2 +- .../Models/ClassTemplateModel.cs | 21 ++-- .../Models/PropertyModel.cs | 4 +- .../Templates/Class.liquid | 97 +++++++++---------- .../Templates/ClassTemplate.cs | 18 ++-- .../CSharp/CSharpGeneratorTests.cs | 2 +- .../NJsonSchema.CodeGeneration.Tests.csproj | 4 + .../packages.config | 4 + .../Models/ClassTemplateModel.cs | 10 +- .../Templates/ClassTemplate.cs | 20 ++-- .../LiquidTemplate.cs | 19 +++- .../Models/ClassTemplateModelBase.cs | 4 +- .../Models/EnumerationItemModel.cs | 2 +- .../Models/PropertyModelBase.cs | 11 ++- .../Models/TemplateModelBase.cs | 8 ++ .../NJsonSchema.CodeGeneration.csproj | 2 +- 16 files changed, 133 insertions(+), 95 deletions(-) create mode 100644 src/NJsonSchema.CodeGeneration.Tests/packages.config create mode 100644 src/NJsonSchema.CodeGeneration/Models/TemplateModelBase.cs diff --git a/src/NJsonSchema.CodeGeneration.CSharp/CSharpGenerator.cs b/src/NJsonSchema.CodeGeneration.CSharp/CSharpGenerator.cs index e7edfa526..cb01ad0ba 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/CSharpGenerator.cs +++ b/src/NJsonSchema.CodeGeneration.CSharp/CSharpGenerator.cs @@ -89,7 +89,7 @@ private TypeGeneratorResult GenerateClass(string typeName) return new TypeGeneratorResult { TypeName = typeName, - BaseTypeName = model.BaseClass, + BaseTypeName = model.BaseClassName, Code = template.Render() }; } diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Models/ClassTemplateModel.cs b/src/NJsonSchema.CodeGeneration.CSharp/Models/ClassTemplateModel.cs index 55896bf79..47cb89a3b 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/Models/ClassTemplateModel.cs +++ b/src/NJsonSchema.CodeGeneration.CSharp/Models/ClassTemplateModel.cs @@ -25,7 +25,7 @@ public class ClassTemplateModel : ClassTemplateModelBase /// The resolver. /// The schema. /// The root object. - public ClassTemplateModel(string typeName, CSharpGeneratorSettings settings, + public ClassTemplateModel(string typeName, CSharpGeneratorSettings settings, CSharpTypeResolver resolver, JsonSchema4 schema, object rootObject) : base(resolver, schema, rootObject) { @@ -33,15 +33,18 @@ public ClassTemplateModel(string typeName, CSharpGeneratorSettings settings, _schema = schema; _settings = settings; - Class = typeName; + ClassName = typeName; Properties = _schema.ActualProperties.Values .Where(p => !p.IsInheritanceDiscriminator) .Select(property => new PropertyModel(this, property, _resolver, _settings)) .ToList(); } + /// Gets the NJsonSchema toolchain version. + public string ToolchainVersion => JsonSchema4.ToolchainVersion; + /// Gets or sets the class name. - public override string Class { get; } + public override string ClassName { get; } /// Gets the namespace. public string Namespace => _settings.Namespace; @@ -50,10 +53,10 @@ public ClassTemplateModel(string typeName, CSharpGeneratorSettings settings, public bool HasAdditionalPropertiesType => _schema.AdditionalPropertiesSchema != null; /// Gets the type of the additional properties. - public string AdditionalPropertiesType => _resolver.Resolve( + public string AdditionalPropertiesType => HasAdditionalPropertiesType ? _resolver.Resolve( _schema.AdditionalPropertiesSchema, _schema.AdditionalPropertiesSchema.IsNullable(_settings.NullHandling), - string.Empty); + string.Empty) : null; /// Gets the property models. public IEnumerable Properties { get; } @@ -66,7 +69,7 @@ public ClassTemplateModel(string typeName, CSharpGeneratorSettings settings, /// Gets a value indicating whether the class style is INPC. /// true if inpc; otherwise, false. - public bool Inpc => _settings.ClassStyle == CSharpClassStyle.Inpc; + public bool RenderInpc => _settings.ClassStyle == CSharpClassStyle.Inpc; /// Gets a value indicating whether the class has discriminator property. public bool HasDiscriminator => !string.IsNullOrEmpty(_schema.Discriminator); @@ -78,19 +81,19 @@ public ClassTemplateModel(string typeName, CSharpGeneratorSettings settings, public bool HasInheritance => _schema.InheritedSchema != null; /// Gets the base class name. - public string BaseClass => HasInheritance ? _resolver.Resolve(_schema.InheritedSchema, false, string.Empty) : null; + public string BaseClassName => HasInheritance ? _resolver.Resolve(_schema.InheritedSchema, false, string.Empty) : null; /// Gets the JSON serializer parameter code. public string JsonSerializerParameterCode => CSharpJsonSerializerGenerator.GenerateJsonSerializerParameterCode( _settings.HandleReferences, _settings.JsonConverters); /// Gets the inheritance code. - public string Inheritance + public string InheritanceCode { get { if (HasInheritance) - return ": " + BaseClass + (_settings.ClassStyle == CSharpClassStyle.Inpc ? ", System.ComponentModel.INotifyPropertyChanged" : ""); + return ": " + BaseClassName + (_settings.ClassStyle == CSharpClassStyle.Inpc ? ", System.ComponentModel.INotifyPropertyChanged" : ""); else return _settings.ClassStyle == CSharpClassStyle.Inpc ? ": System.ComponentModel.INotifyPropertyChanged" : ""; } diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Models/PropertyModel.cs b/src/NJsonSchema.CodeGeneration.CSharp/Models/PropertyModel.cs index e66c10ecc..703aefd4c 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/Models/PropertyModel.cs +++ b/src/NJsonSchema.CodeGeneration.CSharp/Models/PropertyModel.cs @@ -54,7 +54,7 @@ public PropertyModel(ClassTemplateModel classTemplateModel, JsonProperty propert )) == false; /// Gets the json property required. - public string JsonPropertyRequired + public string JsonPropertyRequiredCode { get { @@ -144,7 +144,7 @@ public bool RenderRegularExpressionAttribute } /// Gets the regular expression value for the regular expression attribute. - public string RegularExpressionValue => _property.Pattern.Replace("\"", "\"\""); + public string RegularExpressionValue => _property.Pattern?.Replace("\"", "\"\""); /// Gets a value indicating whether the property type is string enum. public bool IsStringEnum => _property.ActualPropertySchema.IsEnumeration && _property.ActualPropertySchema.Type == JsonObjectType.String; diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.liquid b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.liquid index 95569fc66..797d2b694 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.liquid +++ b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.liquid @@ -1,83 +1,80 @@ -{% if Model.HasDescription %} -/// <#=ConversionUtilities.ConvertCSharpDocBreaks(Model.Description, 0)#> +{% if HasDescription %} +/// {{Description | CSharpDocs}} {% endif %} -{% if Model.HasDiscriminator %} -[Newtonsoft.Json.JsonConverter(typeof(JsonInheritanceConverter), "{{ Model.Discriminator }}")] +{% if HasDiscriminator %} +[Newtonsoft.Json.JsonConverter(typeof(JsonInheritanceConverter), "{{Discriminator}}")] {% endif %} -[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "{{ JsonSchema4.ToolchainVersion }}")] -public partial class {{Model.Class}} {{Model.Inheritance}} +[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "{{ToolchainVersion}}")] +public partial class {{ClassName}} {{InheritanceCode}} { -{% if Model.Inpc -%} -{% for property in Model.Properties %} +{%- if RenderInpc -%} +{%- for property in Properties -%} private {{property.Type}} {{property.FieldName}}{% if property.HasDefaultValue %} = {{property.DefaultValue}}{% endif -%}; -{% endfor %} +{%- endfor -%} -{% endif %} -<#foreach(var property in Model.Properties){#> -<# if(property.HasDescription){#> - /// <#=ConversionUtilities.ConvertCSharpDocBreaks(property.Description, 1)#> -<# }#> - [Newtonsoft.Json.JsonProperty("<#=property.Name#>", Required = <#=property.JsonPropertyRequired#><#if(property.IsStringEnumArray){#>, ItemConverterType = typeof(Newtonsoft.Json.Converters.StringEnumConverter)<#}#>)] -<# if(property.RenderRequiredAttribute){#> +{%- endif -%} +{%- for property in Properties -%} +{%- if property.HasDescription -%} + /// {{property.Description | CSharpDocs}} +{%- endif -%} + [Newtonsoft.Json.JsonProperty("{{property.Name}}", Required = {{property.JsonPropertyRequiredCode}}{% if property.IsStringEnumArray %}, ItemConverterType = typeof(Newtonsoft.Json.Converters.StringEnumConverter){% endif %})] +{%- if property.RenderRequiredAttribute -%} [System.ComponentModel.DataAnnotations.Required] -<# }#> -<# if(property.RenderRangeAttribute){#> - [System.ComponentModel.DataAnnotations.Range(<#=property.RangeMinimumValue #>, <#=property.RangeMaximumValue #>)] -<# }#> -<# if(property.RenderStringLengthAttribute){#> - [System.ComponentModel.DataAnnotations.StringLength(<#=property.StringLengthMaximumValue #><# if(property.StringLengthMinimumValue > 0){ #>, MinimumLength = <#=property.StringLengthMinimumValue #><# } #>)] -<# }#> -<# if(property.RenderRegularExpressionAttribute){#> - [System.ComponentModel.DataAnnotations.RegularExpression(@"<#=property.RegularExpressionValue#>")] -<# }#> -<#if(property.IsStringEnum){#> +{%- endif -%} +{%- if property.RenderRangeAttribute -%} + [System.ComponentModel.DataAnnotations.Range({{property.RangeMinimumValue}}, {{property.RangeMaximumValue}})] +{%- endif -%} +{%- if property.RenderStringLengthAttribute -%} + [System.ComponentModel.DataAnnotations.StringLength({{property.StringLengthMaximumValue}}{% if property.StringLengthMinimumValue > 0 %}, MinimumLength = {{property.StringLengthMinimumValue}}{% endif %})] +{%- endif -%} +{%- if property.RenderRegularExpressionAttribute -%} + [System.ComponentModel.DataAnnotations.RegularExpression(@"{{property.RegularExpressionValue}}")] +{%- endif -%} +{%- if(property.IsStringEnum -%} [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] -<# }#> - public <#=property.Type#> <#=property.PropertyName#><# if(!Model.Inpc){#> { get; <#if(property.HasSetter){#>set; <#}#>}<#if(property.HasDefaultValue){#> = <#=property.DefaultValue#>;<#}#> - -<# }else{#> - +{%- endif -%} + public {{property.Type}} {{property.property_name}}{% if RenderInpc == false %}{ get; {% if property.HasSetter %}set; {% endif %}}{% if property.HasDefaultValue %} = {{property.DefaultValue}};{% endif %}{% else %} { - get { return <#=property.FieldName#>; } -<#if(property.HasSetter){#> + get { return {{property.FieldName}}; } +{%- if property.HasSetter -%} set { - if (<#=property.FieldName#> != value) + if ({{property.FieldName}} != value) { - <#=property.FieldName#> = value; + {{property.FieldName}} = value; RaisePropertyChanged(); } } -<#}#> +{%- endif -%} } -<#}#> +{%- endif -%} -<#}#> -<#if(Model.HasAdditionalPropertiesType){#> - private System.Collections.Generic.IDictionary> _additionalProperties = new System.Collections.Generic.Dictionary>(); +{%- endfor -%} +{%- if HasAdditionalPropertiesType -%} + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); [Newtonsoft.Json.JsonExtensionData] - public System.Collections.Generic.IDictionary> AdditionalProperties + public System.Collections.Generic.IDictionary AdditionalProperties { get { return _additionalProperties; } set { _additionalProperties = value; } } -<#}#> -<#if(Model.Inpc){#> +{%- endif -%} +{%- if RenderInpc -%} public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; -<#}#> +{%- endif -%} public string ToJson() { - return Newtonsoft.Json.JsonConvert.SerializeObject(this<#=Model.JsonSerializerParameterCode#>); + return Newtonsoft.Json.JsonConvert.SerializeObject(this{{JsonSerializerParameterCode}}); } - public static <#=Model.Class#> FromJson(string data) + public static {{ClassName}} FromJson(string data) { - return Newtonsoft.Json.JsonConvert.DeserializeObject<<#=Model.Class#>>(data<#=Model.JsonSerializerParameterCode#>); + return Newtonsoft.Json.JsonConvert.DeserializeObject<{{ClassName}}>(data{{JsonSerializerParameterCode}}); } -<#if(Model.Inpc){#> +{%- if RenderInpc -%} protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) { @@ -85,5 +82,5 @@ public partial class {{Model.Class}} {{Model.Inheritance}} if (handler != null) handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); } -<#}#> +{%- endif -%} } \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Templates/ClassTemplate.cs b/src/NJsonSchema.CodeGeneration.CSharp/Templates/ClassTemplate.cs index 26523c0b2..5ff062b6a 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/Templates/ClassTemplate.cs +++ b/src/NJsonSchema.CodeGeneration.CSharp/Templates/ClassTemplate.cs @@ -75,21 +75,21 @@ public virtual string TransformText() this.Write("\")]\r\npublic partial class "); #line 9 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.CSharp\Templates\ClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(Model.Class)); + this.Write(this.ToStringHelper.ToStringWithCulture(Model.ClassName)); #line default #line hidden this.Write(" "); #line 9 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.CSharp\Templates\ClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(Model.Inheritance)); + this.Write(this.ToStringHelper.ToStringWithCulture(Model.InheritanceCode)); #line default #line hidden this.Write("\r\n{\r\n"); #line 11 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.CSharp\Templates\ClassTemplate.tt" -if(Model.Inpc){ +if(Model.RenderInpc){ #line default #line hidden @@ -182,7 +182,7 @@ public virtual string TransformText() this.Write("\", Required = "); #line 21 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.CSharp\Templates\ClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(property.JsonPropertyRequired)); + this.Write(this.ToStringHelper.ToStringWithCulture(property.JsonPropertyRequiredCode)); #line default #line hidden @@ -329,7 +329,7 @@ public virtual string TransformText() #line hidden #line 37 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.CSharp\Templates\ClassTemplate.tt" - if(!Model.Inpc){ + if(!Model.RenderInpc){ #line default #line hidden @@ -462,7 +462,7 @@ public virtual string TransformText() #line hidden #line 68 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.CSharp\Templates\ClassTemplate.tt" -if(Model.Inpc){ +if(Model.RenderInpc){ #line default #line hidden @@ -485,7 +485,7 @@ public virtual string TransformText() this.Write(");\r\n }\r\n \r\n public static "); #line 77 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.CSharp\Templates\ClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(Model.Class)); + this.Write(this.ToStringHelper.ToStringWithCulture(Model.ClassName)); #line default #line hidden @@ -493,7 +493,7 @@ public virtual string TransformText() "lizeObject<"); #line 79 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.CSharp\Templates\ClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(Model.Class)); + this.Write(this.ToStringHelper.ToStringWithCulture(Model.ClassName)); #line default #line hidden @@ -507,7 +507,7 @@ public virtual string TransformText() this.Write(");\r\n }\r\n"); #line 81 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.CSharp\Templates\ClassTemplate.tt" -if(Model.Inpc){ +if(Model.RenderInpc){ #line default #line hidden diff --git a/src/NJsonSchema.CodeGeneration.Tests/CSharp/CSharpGeneratorTests.cs b/src/NJsonSchema.CodeGeneration.Tests/CSharp/CSharpGeneratorTests.cs index 2dfc2952d..840845488 100644 --- a/src/NJsonSchema.CodeGeneration.Tests/CSharp/CSharpGeneratorTests.cs +++ b/src/NJsonSchema.CodeGeneration.Tests/CSharp/CSharpGeneratorTests.cs @@ -411,7 +411,7 @@ public async Task When_property_has_description_then_csharp_has_xml_comment() //// Arrange var schema = await JsonSchema4.FromTypeAsync(); schema.Properties["Class"].Description = "PropertyDesc."; - var generator = new CSharpGenerator(schema); + var generator = new CSharpGenerator(schema, new CSharpGeneratorSettings { ClassStyle = CSharpClassStyle.Poco }); //// Act var output = generator.GenerateFile("MyClass"); diff --git a/src/NJsonSchema.CodeGeneration.Tests/NJsonSchema.CodeGeneration.Tests.csproj b/src/NJsonSchema.CodeGeneration.Tests/NJsonSchema.CodeGeneration.Tests.csproj index 35261663a..d11eee77e 100644 --- a/src/NJsonSchema.CodeGeneration.Tests/NJsonSchema.CodeGeneration.Tests.csproj +++ b/src/NJsonSchema.CodeGeneration.Tests/NJsonSchema.CodeGeneration.Tests.csproj @@ -42,6 +42,9 @@ NJsonSchema.snk + + ..\packages\DotLiquid.2.0.84\lib\net451\DotLiquid.dll + ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll True @@ -113,6 +116,7 @@ Designer + diff --git a/src/NJsonSchema.CodeGeneration.Tests/packages.config b/src/NJsonSchema.CodeGeneration.Tests/packages.config new file mode 100644 index 000000000..7fa4683fd --- /dev/null +++ b/src/NJsonSchema.CodeGeneration.Tests/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration.TypeScript/Models/ClassTemplateModel.cs b/src/NJsonSchema.CodeGeneration.TypeScript/Models/ClassTemplateModel.cs index e2f72739a..cfe199304 100644 --- a/src/NJsonSchema.CodeGeneration.TypeScript/Models/ClassTemplateModel.cs +++ b/src/NJsonSchema.CodeGeneration.TypeScript/Models/ClassTemplateModel.cs @@ -35,12 +35,12 @@ public ClassTemplateModel(string typeName, string discriminatorName, _schema = schema; _resolver = resolver; - Class = typeName; + ClassName = typeName; DiscriminatorName = discriminatorName; } /// Gets the class name. - public override string Class { get; } + public override string ClassName { get; } /// Gets the name for the discriminator check. public string DiscriminatorName { get; } @@ -74,11 +74,11 @@ public string Inheritance if (HasInheritance) { return " extends " + BaseClass + (GenerateConstructorInterface && _settings.TypeStyle == TypeScriptTypeStyle.Class ? - " implements I" + Class : string.Empty); + " implements I" + ClassName : string.Empty); } return GenerateConstructorInterface && _settings.TypeStyle == TypeScriptTypeStyle.Class ? - " implements I" + Class : string.Empty; + " implements I" + ClassName : string.Empty; } } @@ -120,6 +120,6 @@ public string IndexerPropertyValueType /// Gets the property models. public List Properties => _schema.ActualProperties.Values .Where(v => v.IsInheritanceDiscriminator == false) - .Select(property => new PropertyModel(this, property, Class, _resolver, _settings)).ToList(); + .Select(property => new PropertyModel(this, property, ClassName, _resolver, _settings)).ToList(); } } \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration.TypeScript/Templates/ClassTemplate.cs b/src/NJsonSchema.CodeGeneration.TypeScript/Templates/ClassTemplate.cs index 8c2e2c2cd..0d4a036a5 100644 --- a/src/NJsonSchema.CodeGeneration.TypeScript/Templates/ClassTemplate.cs +++ b/src/NJsonSchema.CodeGeneration.TypeScript/Templates/ClassTemplate.cs @@ -48,7 +48,7 @@ public virtual string TransformText() this.Write("export class "); #line 4 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.TypeScript\Templates\ClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(Model.Class)); + this.Write(this.ToStringHelper.ToStringWithCulture(Model.ClassName)); #line default #line hidden @@ -207,7 +207,7 @@ public virtual string TransformText() this.Write("data?: I"); #line 21 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.TypeScript\Templates\ClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(Model.Class)); + this.Write(this.ToStringHelper.ToStringWithCulture(Model.ClassName)); #line default #line hidden @@ -362,7 +362,7 @@ public virtual string TransformText() this.Write("): "); #line 55 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.TypeScript\Templates\ClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(Model.Class)); + this.Write(this.ToStringHelper.ToStringWithCulture(Model.ClassName)); #line default #line hidden @@ -423,14 +423,14 @@ public virtual string TransformText() this.Write(" return createInstance<"); #line 62 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.TypeScript\Templates\ClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(Model.Class)); + this.Write(this.ToStringHelper.ToStringWithCulture(Model.ClassName)); #line default #line hidden this.Write(">(data, _mappings, "); #line 62 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.TypeScript\Templates\ClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(Model.Class)); + this.Write(this.ToStringHelper.ToStringWithCulture(Model.ClassName)); #line default #line hidden @@ -444,14 +444,14 @@ public virtual string TransformText() this.Write(" return createInstance<"); #line 64 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.TypeScript\Templates\ClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(Model.Class)); + this.Write(this.ToStringHelper.ToStringWithCulture(Model.ClassName)); #line default #line hidden this.Write(">(data, _mappings, "); #line 64 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.TypeScript\Templates\ClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(Model.Class)); + this.Write(this.ToStringHelper.ToStringWithCulture(Model.ClassName)); #line default #line hidden @@ -517,7 +517,7 @@ public virtual string TransformText() this.Write(" let result = new "); #line 76 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.TypeScript\Templates\ClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(Model.Class)); + this.Write(this.ToStringHelper.ToStringWithCulture(Model.ClassName)); #line default #line hidden @@ -607,7 +607,7 @@ public virtual string TransformText() this.Write("\r\n clone() {\r\n const json = this.toJSON();\r\n let result = new "); #line 105 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.TypeScript\Templates\ClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(Model.Class)); + this.Write(this.ToStringHelper.ToStringWithCulture(Model.ClassName)); #line default #line hidden @@ -649,7 +649,7 @@ public virtual string TransformText() this.Write("export interface I"); #line 116 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.TypeScript\Templates\ClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(Model.Class)); + this.Write(this.ToStringHelper.ToStringWithCulture(Model.ClassName)); #line default #line hidden diff --git a/src/NJsonSchema.CodeGeneration/LiquidTemplate.cs b/src/NJsonSchema.CodeGeneration/LiquidTemplate.cs index 64bff97a7..fa3d1f8ba 100644 --- a/src/NJsonSchema.CodeGeneration/LiquidTemplate.cs +++ b/src/NJsonSchema.CodeGeneration/LiquidTemplate.cs @@ -6,6 +6,7 @@ // Rico Suter, mail@rsuter.com //----------------------------------------------------------------------- +using System.Diagnostics; using DotLiquid; namespace NJsonSchema.CodeGeneration @@ -24,8 +25,22 @@ public LiquidTemplate(string template, object model) public string Render() { var tpl = Template.Parse(_template); - var hash = Hash.FromAnonymousObject(new { Model = Hash.FromAnonymousObject(_model) }); - return tpl.Render(hash); + var hash = Hash.FromAnonymousObject(_model); + // TODO: Check models here + return tpl.Render(new RenderParameters + { + LocalVariables = hash, + Filters = new[] { typeof(LiquidFilters) } + }); + } + } + + public static class LiquidFilters + { + public static string CSharpDocs(string input) + { + // TODO: Check if this is really called! + return ConversionUtilities.ConvertCSharpDocBreaks(input, 0); } } } \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration/Models/ClassTemplateModelBase.cs b/src/NJsonSchema.CodeGeneration/Models/ClassTemplateModelBase.cs index 0b7451a11..48c1a16e5 100644 --- a/src/NJsonSchema.CodeGeneration/Models/ClassTemplateModelBase.cs +++ b/src/NJsonSchema.CodeGeneration/Models/ClassTemplateModelBase.cs @@ -12,7 +12,7 @@ namespace NJsonSchema.CodeGeneration.Models { /// The class template base class. - public abstract class ClassTemplateModelBase + public abstract class ClassTemplateModelBase : TemplateModelBase { private readonly JsonSchema4 _schema; private readonly object _rootObject; @@ -30,7 +30,7 @@ protected ClassTemplateModelBase(ITypeResolver resolver, JsonSchema4 schema, obj } /// Gets the class. - public abstract string Class { get; } + public abstract string ClassName { get; } /// Gets the derived class names. public List DerivedClassNames => _schema.GetDerivedSchemas(_rootObject, _resolver) diff --git a/src/NJsonSchema.CodeGeneration/Models/EnumerationItemModel.cs b/src/NJsonSchema.CodeGeneration/Models/EnumerationItemModel.cs index 25257c4a9..9e28746d9 100644 --- a/src/NJsonSchema.CodeGeneration/Models/EnumerationItemModel.cs +++ b/src/NJsonSchema.CodeGeneration/Models/EnumerationItemModel.cs @@ -9,7 +9,7 @@ namespace NJsonSchema.CodeGeneration.Models { /// Describes an enumeration entry. - public class EnumerationItemModel + public class EnumerationItemModel : TemplateModelBase { /// Gets or sets the name. public string Name { get; set; } diff --git a/src/NJsonSchema.CodeGeneration/Models/PropertyModelBase.cs b/src/NJsonSchema.CodeGeneration/Models/PropertyModelBase.cs index c4a23ade2..5c4900a0d 100644 --- a/src/NJsonSchema.CodeGeneration/Models/PropertyModelBase.cs +++ b/src/NJsonSchema.CodeGeneration/Models/PropertyModelBase.cs @@ -7,11 +7,12 @@ //----------------------------------------------------------------------- using System; +using DotLiquid; namespace NJsonSchema.CodeGeneration.Models { /// The property template model base class. - public abstract class PropertyModelBase + public abstract class PropertyModelBase : TemplateModelBase { private readonly ClassTemplateModelBase _classTemplateModel; private readonly JsonProperty _property; @@ -64,7 +65,7 @@ protected string GetTypeNameHint() if (_property.IsEnumeration == false) return propertyName; - var className = _classTemplateModel.Class; + var className = _classTemplateModel.ClassName; if (className.Contains("Anonymous")) return propertyName; @@ -73,5 +74,11 @@ protected string GetTypeNameHint() return className + ConversionUtilities.ConvertToUpperCamelCase(PropertyName, false); } + + public object ToLiquid() + { + var x = Hash.FromAnonymousObject(this); + return x; + } } } \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration/Models/TemplateModelBase.cs b/src/NJsonSchema.CodeGeneration/Models/TemplateModelBase.cs new file mode 100644 index 000000000..3f9c4c8a7 --- /dev/null +++ b/src/NJsonSchema.CodeGeneration/Models/TemplateModelBase.cs @@ -0,0 +1,8 @@ +using DotLiquid; + +namespace NJsonSchema.CodeGeneration.Models +{ + public class TemplateModelBase : Drop + { + } +} diff --git a/src/NJsonSchema.CodeGeneration/NJsonSchema.CodeGeneration.csproj b/src/NJsonSchema.CodeGeneration/NJsonSchema.CodeGeneration.csproj index 15b344a63..df994e997 100644 --- a/src/NJsonSchema.CodeGeneration/NJsonSchema.CodeGeneration.csproj +++ b/src/NJsonSchema.CodeGeneration/NJsonSchema.CodeGeneration.csproj @@ -14,7 +14,7 @@ True - + From c325b816259ec90a1c18aaae086959e48fd7e579 Mon Sep 17 00:00:00 2001 From: Rico Suter Date: Fri, 15 Sep 2017 13:37:44 +0200 Subject: [PATCH 03/19] Added liquid hash creator --- .../Models/ClassTemplateModel.cs | 2 +- .../Templates/Class.liquid | 4 +- .../Templates/ClassTemplate.cs | 18 +++--- .../Templates/ClassTemplate.tt | 16 +++--- .../CSharp/CSharpGeneratorTests.cs | 6 ++ .../DefaultTemplateFactory.cs | 5 +- src/NJsonSchema.CodeGeneration/LiquidHash.cs | 55 +++++++++++++++++++ .../LiquidTemplate.cs | 4 +- .../Models/TemplateModelBase.cs | 2 +- 9 files changed, 87 insertions(+), 25 deletions(-) create mode 100644 src/NJsonSchema.CodeGeneration/LiquidHash.cs diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Models/ClassTemplateModel.cs b/src/NJsonSchema.CodeGeneration.CSharp/Models/ClassTemplateModel.cs index 47cb89a3b..603f5e254 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/Models/ClassTemplateModel.cs +++ b/src/NJsonSchema.CodeGeneration.CSharp/Models/ClassTemplateModel.cs @@ -55,7 +55,7 @@ public ClassTemplateModel(string typeName, CSharpGeneratorSettings settings, /// Gets the type of the additional properties. public string AdditionalPropertiesType => HasAdditionalPropertiesType ? _resolver.Resolve( _schema.AdditionalPropertiesSchema, - _schema.AdditionalPropertiesSchema.IsNullable(_settings.NullHandling), + _schema.AdditionalPropertiesSchema.IsNullable(_settings.SchemaType), string.Empty) : null; /// Gets the property models. diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.liquid b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.liquid index 797d2b694..1051eb07f 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.liquid +++ b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.liquid @@ -33,7 +33,7 @@ public partial class {{ClassName}} {{InheritanceCode}} {%- if(property.IsStringEnum -%} [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] {%- endif -%} - public {{property.Type}} {{property.property_name}}{% if RenderInpc == false %}{ get; {% if property.HasSetter %}set; {% endif %}}{% if property.HasDefaultValue %} = {{property.DefaultValue}};{% endif %}{% else %} + public {{property.Type}} {{property.PropertyName}}{% if RenderInpc == false %} { get; {% if property.HasSetter %}set; {% endif %}}{% if property.HasDefaultValue %} = {{property.DefaultValue}};{% endif %}{% else %} { get { return {{property.FieldName}}; } {%- if property.HasSetter -%} @@ -47,7 +47,7 @@ public partial class {{ClassName}} {{InheritanceCode}} } {%- endif -%} } -{%- endif -%} +{%- endif %} {%- endfor -%} {%- if HasAdditionalPropertiesType -%} diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Templates/ClassTemplate.cs b/src/NJsonSchema.CodeGeneration.CSharp/Templates/ClassTemplate.cs index 85bcc0e80..ffeb37e14 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/Templates/ClassTemplate.cs +++ b/src/NJsonSchema.CodeGeneration.CSharp/Templates/ClassTemplate.cs @@ -102,21 +102,21 @@ public virtual string TransformText() this.Write("\")]\r\npublic partial class "); #line 12 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.CSharp\Templates\ClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(Model.Class)); + this.Write(this.ToStringHelper.ToStringWithCulture(Model.ClassName)); #line default #line hidden this.Write(" "); #line 12 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.CSharp\Templates\ClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(Model.Inheritance)); + this.Write(this.ToStringHelper.ToStringWithCulture(Model.InheritanceCode)); #line default #line hidden this.Write("\r\n{\r\n"); #line 14 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.CSharp\Templates\ClassTemplate.tt" -if(Model.Inpc){ +if(Model.RenderInpc){ #line default #line hidden @@ -209,7 +209,7 @@ public virtual string TransformText() this.Write("\", Required = "); #line 24 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.CSharp\Templates\ClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(property.JsonPropertyRequired)); + this.Write(this.ToStringHelper.ToStringWithCulture(property.JsonPropertyRequiredCode)); #line default #line hidden @@ -356,7 +356,7 @@ public virtual string TransformText() #line hidden #line 40 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.CSharp\Templates\ClassTemplate.tt" - if(!Model.Inpc){ + if(!Model.RenderInpc){ #line default #line hidden @@ -489,7 +489,7 @@ public virtual string TransformText() #line hidden #line 71 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.CSharp\Templates\ClassTemplate.tt" -if(Model.Inpc){ +if(Model.RenderInpc){ #line default #line hidden @@ -512,7 +512,7 @@ public virtual string TransformText() this.Write(");\r\n }\r\n \r\n public static "); #line 80 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.CSharp\Templates\ClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(Model.Class)); + this.Write(this.ToStringHelper.ToStringWithCulture(Model.ClassName)); #line default #line hidden @@ -520,7 +520,7 @@ public virtual string TransformText() "lizeObject<"); #line 82 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.CSharp\Templates\ClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(Model.Class)); + this.Write(this.ToStringHelper.ToStringWithCulture(Model.ClassName)); #line default #line hidden @@ -534,7 +534,7 @@ public virtual string TransformText() this.Write(");\r\n }\r\n"); #line 84 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.CSharp\Templates\ClassTemplate.tt" -if(Model.Inpc){ +if(Model.RenderInpc){ #line default #line hidden diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Templates/ClassTemplate.tt b/src/NJsonSchema.CodeGeneration.CSharp/Templates/ClassTemplate.tt index 570f9491d..b885ede82 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/Templates/ClassTemplate.tt +++ b/src/NJsonSchema.CodeGeneration.CSharp/Templates/ClassTemplate.tt @@ -9,9 +9,9 @@ <# }#> <#}#> [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "<#=JsonSchema4.ToolchainVersion#>")] -public partial class <#=Model.Class#> <#=Model.Inheritance#> +public partial class <#=Model.ClassName#> <#=Model.InheritanceCode#> { -<#if(Model.Inpc){#> +<#if(Model.RenderInpc){#> <#foreach(var property in Model.Properties){#> private <#=property.Type#> <#=property.FieldName#><#if(property.HasDefaultValue){#> = <#=property.DefaultValue#><#}#>; <#}#> @@ -21,7 +21,7 @@ public partial class <#=Model.Class#> <#=Model.Inheritance#> <# if(property.HasDescription){#> /// <#=ConversionUtilities.ConvertCSharpDocBreaks(property.Description, 1)#> <# }#> - [Newtonsoft.Json.JsonProperty("<#=property.Name#>", Required = <#=property.JsonPropertyRequired#><#if(property.IsStringEnumArray){#>, ItemConverterType = typeof(Newtonsoft.Json.Converters.StringEnumConverter)<#}#>)] + [Newtonsoft.Json.JsonProperty("<#=property.Name#>", Required = <#=property.JsonPropertyRequiredCode#><#if(property.IsStringEnumArray){#>, ItemConverterType = typeof(Newtonsoft.Json.Converters.StringEnumConverter)<#}#>)] <# if(property.RenderRequiredAttribute){#> [System.ComponentModel.DataAnnotations.Required] <# }#> @@ -37,7 +37,7 @@ public partial class <#=Model.Class#> <#=Model.Inheritance#> <#if(property.IsStringEnum){#> [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] <# }#> - public <#=property.Type#> <#=property.PropertyName#><# if(!Model.Inpc){#> { get; <#if(property.HasSetter){#>set; <#}#>}<#if(property.HasDefaultValue){#> = <#=property.DefaultValue#>;<#}#> + public <#=property.Type#> <#=property.PropertyName#><# if(!Model.RenderInpc){#> { get; <#if(property.HasSetter){#>set; <#}#>}<#if(property.HasDefaultValue){#> = <#=property.DefaultValue#>;<#}#> <# }else{#> @@ -68,7 +68,7 @@ public partial class <#=Model.Class#> <#=Model.Inheritance#> } <#}#> -<#if(Model.Inpc){#> +<#if(Model.RenderInpc){#> public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; <#}#> @@ -77,11 +77,11 @@ public partial class <#=Model.Class#> <#=Model.Inheritance#> return Newtonsoft.Json.JsonConvert.SerializeObject(this<#=Model.JsonSerializerParameterCode#>); } - public static <#=Model.Class#> FromJson(string data) + public static <#=Model.ClassName#> FromJson(string data) { - return Newtonsoft.Json.JsonConvert.DeserializeObject<<#=Model.Class#>>(data<#=Model.JsonSerializerParameterCode#>); + return Newtonsoft.Json.JsonConvert.DeserializeObject<<#=Model.ClassName#>>(data<#=Model.JsonSerializerParameterCode#>); } -<#if(Model.Inpc){#> +<#if(Model.RenderInpc){#> protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) { diff --git a/src/NJsonSchema.CodeGeneration.Tests/CSharp/CSharpGeneratorTests.cs b/src/NJsonSchema.CodeGeneration.Tests/CSharp/CSharpGeneratorTests.cs index 443dd34cb..0c54ec5af 100644 --- a/src/NJsonSchema.CodeGeneration.Tests/CSharp/CSharpGeneratorTests.cs +++ b/src/NJsonSchema.CodeGeneration.Tests/CSharp/CSharpGeneratorTests.cs @@ -85,7 +85,13 @@ public async Task When_all_of_has_multiple_refs_then_the_properties_should_expan var schema = await JsonSchema4.FromJsonAsync(json); var settings = new CSharpGeneratorSettings { ClassStyle = CSharpClassStyle.Poco, Namespace = "ns" }; var generator = new CSharpGenerator(schema, settings); + + DefaultTemplateFactory.UseLiquid = false; var output = generator.GenerateFile("Foo"); + DefaultTemplateFactory.UseLiquid = true; + + var output2 = generator.GenerateFile("Foo"); + Assert.AreEqual(output, output2); //// Assert Assert.IsTrue(output.Contains("public partial class TAgg")); diff --git a/src/NJsonSchema.CodeGeneration/DefaultTemplateFactory.cs b/src/NJsonSchema.CodeGeneration/DefaultTemplateFactory.cs index 28e6f8f3c..9de61c96d 100644 --- a/src/NJsonSchema.CodeGeneration/DefaultTemplateFactory.cs +++ b/src/NJsonSchema.CodeGeneration/DefaultTemplateFactory.cs @@ -9,13 +9,14 @@ using System; using System.IO; using System.Reflection; -using DotLiquid; namespace NJsonSchema.CodeGeneration { /// The default template factory which loads templates from embedded resources. public class DefaultTemplateFactory : ITemplateFactory { + public static bool UseLiquid { get; set; } = true; + /// Creates a template for the given language, template name and template model. /// Supports NJsonSchema and NSwag embedded templates. /// The package name (i.e. language). @@ -28,7 +29,7 @@ public virtual ITemplate CreateTemplate(string package, string template, object var assembly = Assembly.Load(new AssemblyName("NJsonSchema.CodeGeneration." + package)); var resourceName = "NJsonSchema.CodeGeneration." + package + ".Templates." + template + ".liquid"; var resource = assembly.GetManifestResourceStream(resourceName); - if (resource != null) + if (resource != null && UseLiquid) { using (var reader = new StreamReader(resource)) return new LiquidTemplate(reader.ReadToEnd(), model); diff --git a/src/NJsonSchema.CodeGeneration/LiquidHash.cs b/src/NJsonSchema.CodeGeneration/LiquidHash.cs new file mode 100644 index 000000000..a4161e1a4 --- /dev/null +++ b/src/NJsonSchema.CodeGeneration/LiquidHash.cs @@ -0,0 +1,55 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Rico Suter. All rights reserved. +// +// https://github.com/rsuter/NJsonSchema/blob/master/LICENSE.md +// Rico Suter, mail@rsuter.com +//----------------------------------------------------------------------- + +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using DotLiquid; + +namespace NJsonSchema.CodeGeneration +{ + internal class LiquidHash + { + public static Hash FromObject(object obj) + { + return FromObject(obj, new Dictionary()); + } + + private static Hash FromObject(object obj, Dictionary cache) + { + if (cache == null) + cache = new Dictionary(); + + if (cache.ContainsKey(obj)) + return cache[obj]; + + var hash = new Hash(); + foreach (var property in obj.GetType().GetRuntimeProperties().Where(p => p.CanRead)) + { + var value = property.GetValue(obj, null); + if (value is IEnumerable && !(value is string)) + { + var list = new List(); + foreach (var item in (IEnumerable)value) + { + list.Add(FromObject(item, cache)); + } + hash[property.Name] = list; + } + else if (value != null && property.PropertyType.GetTypeInfo().IsClass && !(value is string)) + hash[property.Name] = FromObject(value, cache); + else + hash[property.Name] = value; + } + + cache[obj] = hash; + return hash; + } + } +} \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration/LiquidTemplate.cs b/src/NJsonSchema.CodeGeneration/LiquidTemplate.cs index fa3d1f8ba..649d812d0 100644 --- a/src/NJsonSchema.CodeGeneration/LiquidTemplate.cs +++ b/src/NJsonSchema.CodeGeneration/LiquidTemplate.cs @@ -25,13 +25,13 @@ public LiquidTemplate(string template, object model) public string Render() { var tpl = Template.Parse(_template); - var hash = Hash.FromAnonymousObject(_model); + var hash = LiquidHash.FromObject(_model); // TODO: Check models here return tpl.Render(new RenderParameters { LocalVariables = hash, Filters = new[] { typeof(LiquidFilters) } - }); + }).Trim('\r', '\n', ' '); } } diff --git a/src/NJsonSchema.CodeGeneration/Models/TemplateModelBase.cs b/src/NJsonSchema.CodeGeneration/Models/TemplateModelBase.cs index 3f9c4c8a7..cd2822c54 100644 --- a/src/NJsonSchema.CodeGeneration/Models/TemplateModelBase.cs +++ b/src/NJsonSchema.CodeGeneration/Models/TemplateModelBase.cs @@ -2,7 +2,7 @@ namespace NJsonSchema.CodeGeneration.Models { - public class TemplateModelBase : Drop + public class TemplateModelBase { } } From f88d5b109c9ed10c5549445da3b77da5365216bf Mon Sep 17 00:00:00 2001 From: Rico Suter Date: Mon, 9 Oct 2017 20:19:56 +0200 Subject: [PATCH 04/19] Improved templates --- .../CSharpGeneratorSettings.cs | 2 +- .../Templates/Class.liquid | 40 ++++++++------- .../Templates/ClassTemplate.cs | 18 +++---- .../Templates/ClassTemplate.tt | 2 +- .../CSharp/CSharpGeneratorTests.cs | 6 --- .../Templates/ClassTemplate.cs | 20 ++++---- .../Templates/ClassTemplate.tt | 16 +++--- .../Templates/InterfaceTemplate.cs | 2 +- .../Templates/InterfaceTemplate.tt | 2 +- .../Templates/KnockoutClassTemplate.cs | 16 +++--- .../Templates/KnockoutClassTemplate.tt | 12 ++--- .../TypeScriptGeneratorSettings.cs | 2 +- .../CodeGeneratorSettingsBase.cs | 3 ++ .../DefaultTemplateFactory.cs | 50 +++++++++++++------ 14 files changed, 107 insertions(+), 84 deletions(-) diff --git a/src/NJsonSchema.CodeGeneration.CSharp/CSharpGeneratorSettings.cs b/src/NJsonSchema.CodeGeneration.CSharp/CSharpGeneratorSettings.cs index 634698c8c..f5dcf27f6 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/CSharpGeneratorSettings.cs +++ b/src/NJsonSchema.CodeGeneration.CSharp/CSharpGeneratorSettings.cs @@ -27,7 +27,7 @@ public CSharpGeneratorSettings() ClassStyle = CSharpClassStyle.Inpc; PropertyNameGenerator = new CSharpPropertyNameGenerator(); - TemplateFactory = new DefaultTemplateFactory(); + TemplateFactory = new DefaultTemplateFactory((CodeGeneratorSettingsBase)this); } /// Gets or sets the .NET namespace of the generated types. diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.liquid b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.liquid index 1051eb07f..a840ddb95 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.liquid +++ b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.liquid @@ -1,47 +1,53 @@ {% if HasDescription %} -/// {{Description | CSharpDocs}} +/// {{ Description | CSharpDocs }} {% endif %} {% if HasDiscriminator %} -[Newtonsoft.Json.JsonConverter(typeof(JsonInheritanceConverter), "{{Discriminator}}")] +[Newtonsoft.Json.JsonConverter(typeof(JsonInheritanceConverter), "{{ Discriminator }}")] +{%- for derivedClass in DerivedClasses -%} +[JsonInheritanceAttribute("{{ derivedClass.Key }}", typeof({{ derivedClass.Value }}))] +{%- endfor -%} {% endif %} [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "{{ToolchainVersion}}")] public partial class {{ClassName}} {{InheritanceCode}} { {%- if RenderInpc -%} {%- for property in Properties -%} - private {{property.Type}} {{property.FieldName}}{% if property.HasDefaultValue %} = {{property.DefaultValue}}{% endif -%}; + private {{ property.Type }} {{ property.FieldName }}{% if property.HasDefaultValue %} = {{ property.DefaultValue }}{% endif -%}; {%- endfor -%} {%- endif -%} {%- for property in Properties -%} {%- if property.HasDescription -%} - /// {{property.Description | CSharpDocs}} + /// {{ property.Description | CSharpDocs }} {%- endif -%} - [Newtonsoft.Json.JsonProperty("{{property.Name}}", Required = {{property.JsonPropertyRequiredCode}}{% if property.IsStringEnumArray %}, ItemConverterType = typeof(Newtonsoft.Json.Converters.StringEnumConverter){% endif %})] + [Newtonsoft.Json.JsonProperty("{{ property.Name }}", Required = {{ property.JsonPropertyRequiredCode }}{% if property.IsStringEnumArray %}, ItemConverterType = typeof(Newtonsoft.Json.Converters.StringEnumConverter){% endif %})] {%- if property.RenderRequiredAttribute -%} [System.ComponentModel.DataAnnotations.Required] {%- endif -%} {%- if property.RenderRangeAttribute -%} - [System.ComponentModel.DataAnnotations.Range({{property.RangeMinimumValue}}, {{property.RangeMaximumValue}})] + [System.ComponentModel.DataAnnotations.Range({{ property.RangeMinimumValue }}, {{ property.RangeMaximumValue }})] {%- endif -%} {%- if property.RenderStringLengthAttribute -%} - [System.ComponentModel.DataAnnotations.StringLength({{property.StringLengthMaximumValue}}{% if property.StringLengthMinimumValue > 0 %}, MinimumLength = {{property.StringLengthMinimumValue}}{% endif %})] + [System.ComponentModel.DataAnnotations.StringLength({{ property.StringLengthMaximumValue }}{% if property.StringLengthMinimumValue > 0 %}, MinimumLength = {{ property.StringLengthMinimumValue }}{% endif %})] {%- endif -%} {%- if property.RenderRegularExpressionAttribute -%} - [System.ComponentModel.DataAnnotations.RegularExpression(@"{{property.RegularExpressionValue}}")] + [System.ComponentModel.DataAnnotations.RegularExpression(@"{{ property.RegularExpressionValue }}")] {%- endif -%} {%- if(property.IsStringEnum -%} [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] {%- endif -%} - public {{property.Type}} {{property.PropertyName}}{% if RenderInpc == false %} { get; {% if property.HasSetter %}set; {% endif %}}{% if property.HasDefaultValue %} = {{property.DefaultValue}};{% endif %}{% else %} +{%- if(property.IsDate -%} + [Newtonsoft.Json.JsonConverter(typeof(DateFormatConverter))] +{%- endif -%} + public {{ property.Type }} {{ property.PropertyName }}{% if RenderInpc == false %} { get; {% if property.HasSetter %}set; {% endif %}}{% if property.HasDefaultValue %} = {{ property.DefaultValue }};{% endif %}{% else %} { - get { return {{property.FieldName}}; } + get { return {{ property.FieldName }}; } {%- if property.HasSetter -%} set { - if ({{property.FieldName}} != value) + if ({{ property.FieldName }} != value) { - {{property.FieldName}} = value; + {{ property.FieldName }} = value; RaisePropertyChanged(); } } @@ -51,10 +57,10 @@ public partial class {{ClassName}} {{InheritanceCode}} {%- endfor -%} {%- if HasAdditionalPropertiesType -%} - private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); + private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); [Newtonsoft.Json.JsonExtensionData] - public System.Collections.Generic.IDictionary AdditionalProperties + public System.Collections.Generic.IDictionary AdditionalProperties { get { return _additionalProperties; } set { _additionalProperties = value; } @@ -67,12 +73,12 @@ public partial class {{ClassName}} {{InheritanceCode}} {%- endif -%} public string ToJson() { - return Newtonsoft.Json.JsonConvert.SerializeObject(this{{JsonSerializerParameterCode}}); + return Newtonsoft.Json.JsonConvert.SerializeObject(this{{ JsonSerializerParameterCode }}); } - public static {{ClassName}} FromJson(string data) + public static {{ ClassName }} FromJson(string data) { - return Newtonsoft.Json.JsonConvert.DeserializeObject<{{ClassName}}>(data{{JsonSerializerParameterCode}}); + return Newtonsoft.Json.JsonConvert.DeserializeObject<{{ ClassName }}>(data{{ JsonSerializerParameterCode }}); } {%- if RenderInpc -%} diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Templates/ClassTemplate.cs b/src/NJsonSchema.CodeGeneration.CSharp/Templates/ClassTemplate.cs index 9a6e2c35e..841b8f1db 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/Templates/ClassTemplate.cs +++ b/src/NJsonSchema.CodeGeneration.CSharp/Templates/ClassTemplate.cs @@ -102,21 +102,21 @@ public virtual string TransformText() this.Write("\")]\r\npublic partial class "); #line 12 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.CSharp\Templates\ClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(Model.Class)); + this.Write(this.ToStringHelper.ToStringWithCulture(Model.ClassName)); #line default #line hidden this.Write(" "); #line 12 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.CSharp\Templates\ClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(Model.Inheritance)); + this.Write(this.ToStringHelper.ToStringWithCulture(Model.InheritanceCode)); #line default #line hidden this.Write("\r\n{\r\n"); #line 14 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.CSharp\Templates\ClassTemplate.tt" -if(Model.Inpc){ +if(Model.RenderInpc){ #line default #line hidden @@ -209,7 +209,7 @@ public virtual string TransformText() this.Write("\", Required = "); #line 24 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.CSharp\Templates\ClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(property.JsonPropertyRequired)); + this.Write(this.ToStringHelper.ToStringWithCulture(property.JsonPropertyRequiredCode)); #line default #line hidden @@ -369,7 +369,7 @@ public virtual string TransformText() #line hidden #line 43 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.CSharp\Templates\ClassTemplate.tt" - if(!Model.Inpc){ + if(!Model.RenderInpc){ #line default #line hidden @@ -502,7 +502,7 @@ public virtual string TransformText() #line hidden #line 74 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.CSharp\Templates\ClassTemplate.tt" -if(Model.Inpc){ +if(Model.RenderInpc){ #line default #line hidden @@ -525,7 +525,7 @@ public virtual string TransformText() this.Write(");\r\n }\r\n \r\n public static "); #line 83 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.CSharp\Templates\ClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(Model.Class)); + this.Write(this.ToStringHelper.ToStringWithCulture(Model.ClassName)); #line default #line hidden @@ -533,7 +533,7 @@ public virtual string TransformText() "lizeObject<"); #line 85 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.CSharp\Templates\ClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(Model.Class)); + this.Write(this.ToStringHelper.ToStringWithCulture(Model.ClassName)); #line default #line hidden @@ -547,7 +547,7 @@ public virtual string TransformText() this.Write(");\r\n }\r\n"); #line 87 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.CSharp\Templates\ClassTemplate.tt" -if(Model.Inpc){ +if(Model.RenderInpc){ #line default #line hidden diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Templates/ClassTemplate.tt b/src/NJsonSchema.CodeGeneration.CSharp/Templates/ClassTemplate.tt index a843689f6..c4cae56e1 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/Templates/ClassTemplate.tt +++ b/src/NJsonSchema.CodeGeneration.CSharp/Templates/ClassTemplate.tt @@ -40,7 +40,7 @@ public partial class <#=Model.ClassName#> <#=Model.InheritanceCode#> <# if(property.IsDate){#> [Newtonsoft.Json.JsonConverter(typeof(DateFormatConverter))] <# }#> - public <#=property.Type#> <#=property.PropertyName#><# if(!Model.Inpc){#> { get; <#if(property.HasSetter){#>set; <#}#>}<#if(property.HasDefaultValue){#> = <#=property.DefaultValue#>;<#}#> + public <#=property.Type#> <#=property.PropertyName#><# if(!Model.RenderInpc){#> { get; <#if(property.HasSetter){#>set; <#}#>}<#if(property.HasDefaultValue){#> = <#=property.DefaultValue#>;<#}#> <# }else{#> diff --git a/src/NJsonSchema.CodeGeneration.Tests/CSharp/CSharpGeneratorTests.cs b/src/NJsonSchema.CodeGeneration.Tests/CSharp/CSharpGeneratorTests.cs index bf547c5c1..a97d90189 100644 --- a/src/NJsonSchema.CodeGeneration.Tests/CSharp/CSharpGeneratorTests.cs +++ b/src/NJsonSchema.CodeGeneration.Tests/CSharp/CSharpGeneratorTests.cs @@ -85,13 +85,7 @@ public async Task When_all_of_has_multiple_refs_then_the_properties_should_expan var schema = await JsonSchema4.FromJsonAsync(json); var settings = new CSharpGeneratorSettings { ClassStyle = CSharpClassStyle.Poco, Namespace = "ns" }; var generator = new CSharpGenerator(schema, settings); - - DefaultTemplateFactory.UseLiquid = false; var output = generator.GenerateFile("Foo"); - DefaultTemplateFactory.UseLiquid = true; - - var output2 = generator.GenerateFile("Foo"); - Assert.AreEqual(output, output2); //// Assert Assert.IsTrue(output.Contains("public partial class TAgg")); diff --git a/src/NJsonSchema.CodeGeneration.TypeScript/Templates/ClassTemplate.cs b/src/NJsonSchema.CodeGeneration.TypeScript/Templates/ClassTemplate.cs index c3eb160bc..167c8cc46 100644 --- a/src/NJsonSchema.CodeGeneration.TypeScript/Templates/ClassTemplate.cs +++ b/src/NJsonSchema.CodeGeneration.TypeScript/Templates/ClassTemplate.cs @@ -48,7 +48,7 @@ public virtual string TransformText() this.Write("export class "); #line 4 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.TypeScript\Templates\ClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(Model.Class)); + this.Write(this.ToStringHelper.ToStringWithCulture(Model.ClassName)); #line default #line hidden @@ -207,7 +207,7 @@ public virtual string TransformText() this.Write("data?: I"); #line 21 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.TypeScript\Templates\ClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(Model.Class)); + this.Write(this.ToStringHelper.ToStringWithCulture(Model.ClassName)); #line default #line hidden @@ -580,7 +580,7 @@ public virtual string TransformText() this.Write("): "); #line 82 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.TypeScript\Templates\ClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(Model.Class)); + this.Write(this.ToStringHelper.ToStringWithCulture(Model.ClassName)); #line default #line hidden @@ -641,14 +641,14 @@ public virtual string TransformText() this.Write(" return createInstance<"); #line 89 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.TypeScript\Templates\ClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(Model.Class)); + this.Write(this.ToStringHelper.ToStringWithCulture(Model.ClassName)); #line default #line hidden this.Write(">(data, _mappings, "); #line 89 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.TypeScript\Templates\ClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(Model.Class)); + this.Write(this.ToStringHelper.ToStringWithCulture(Model.ClassName)); #line default #line hidden @@ -662,14 +662,14 @@ public virtual string TransformText() this.Write(" return createInstance<"); #line 91 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.TypeScript\Templates\ClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(Model.Class)); + this.Write(this.ToStringHelper.ToStringWithCulture(Model.ClassName)); #line default #line hidden this.Write(">(data, _mappings, "); #line 91 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.TypeScript\Templates\ClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(Model.Class)); + this.Write(this.ToStringHelper.ToStringWithCulture(Model.ClassName)); #line default #line hidden @@ -735,7 +735,7 @@ public virtual string TransformText() this.Write(" let result = new "); #line 103 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.TypeScript\Templates\ClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(Model.Class)); + this.Write(this.ToStringHelper.ToStringWithCulture(Model.ClassName)); #line default #line hidden @@ -825,7 +825,7 @@ public virtual string TransformText() this.Write("\r\n clone() {\r\n const json = this.toJSON();\r\n let result = new "); #line 132 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.TypeScript\Templates\ClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(Model.Class)); + this.Write(this.ToStringHelper.ToStringWithCulture(Model.ClassName)); #line default #line hidden @@ -867,7 +867,7 @@ public virtual string TransformText() this.Write("export interface I"); #line 143 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.TypeScript\Templates\ClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(Model.Class)); + this.Write(this.ToStringHelper.ToStringWithCulture(Model.ClassName)); #line default #line hidden diff --git a/src/NJsonSchema.CodeGeneration.TypeScript/Templates/ClassTemplate.tt b/src/NJsonSchema.CodeGeneration.TypeScript/Templates/ClassTemplate.tt index 71bf5ee0d..e7d3518dd 100644 --- a/src/NJsonSchema.CodeGeneration.TypeScript/Templates/ClassTemplate.tt +++ b/src/NJsonSchema.CodeGeneration.TypeScript/Templates/ClassTemplate.tt @@ -1,7 +1,7 @@ <#@ template visibility="internal" #> <#if(Model.HasDescription){#> /** <#=Model.Description#> */ -<#}#>export class <#=Model.Class#><#=Model.Inheritance#> { +<#}#>export class <#=Model.ClassName#><#=Model.Inheritance#> { <#foreach(var property in Model.Properties){#> <# if(property.HasDescription){#> /** <#=property.Description#> */ @@ -18,7 +18,7 @@ <#}#> <#if(Model.GenerateConstructorInterface || Model.HasBaseDiscriminator){#> - constructor(<#if(Model.GenerateConstructorInterface){#>data?: I<#=Model.Class#><#}#>) { + constructor(<#if(Model.GenerateConstructorInterface){#>data?: I<#=Model.ClassName#><#}#>) { <# if(Model.HasInheritance){#> super(<#if(Model.GenerateConstructorInterface){#>data<#}#>); <# }else if(Model.GenerateConstructorInterface){#> @@ -79,16 +79,16 @@ } } - static fromJS(data: any<#if(Model.HandleReferences){#>, _mappings?: any<#}#>): <#=Model.Class#> { + static fromJS(data: any<#if(Model.HandleReferences){#>, _mappings?: any<#}#>): <#=Model.ClassName#> { <#if(Model.HandleReferences){#> <# if(Model.HasBaseDiscriminator){#> <# foreach (var derivedClass in Model.DerivedClasses){#> if (data["<#=Model.BaseDiscriminator#>"] === "<#=derivedClass.Key#>") return createInstance<<#=derivedClass.Value#>>(data, _mappings, <#=derivedClass.Value#>); <# }#> - return createInstance<<#=Model.Class#>>(data, _mappings, <#=Model.Class#>); + return createInstance<<#=Model.ClassName#>>(data, _mappings, <#=Model.ClassName#>); <# }else{#> - return createInstance<<#=Model.Class#>>(data, _mappings, <#=Model.Class#>); + return createInstance<<#=Model.ClassName#>>(data, _mappings, <#=Model.ClassName#>); <# }#> <#}else{#> <# if(Model.HasBaseDiscriminator){#> @@ -100,7 +100,7 @@ } <# }#> <# }#> - let result = new <#=Model.Class#>(); + let result = new <#=Model.ClassName#>(); result.init(data); return result; <#}#> @@ -129,7 +129,7 @@ clone() { const json = this.toJSON(); - let result = new <#=Model.Class#>(); + let result = new <#=Model.ClassName#>(); result.init(json); return result; } @@ -140,7 +140,7 @@ <# if(Model.HasDescription){#> /** <#=Model.Description#> */ <# }#> -export interface I<#=Model.Class#><#=Model.InterfaceInheritance#> { +export interface I<#=Model.ClassName#><#=Model.InterfaceInheritance#> { <# foreach(var property in Model.Properties){#> <# if(property.HasDescription){#> /** <#=property.Description#> */ diff --git a/src/NJsonSchema.CodeGeneration.TypeScript/Templates/InterfaceTemplate.cs b/src/NJsonSchema.CodeGeneration.TypeScript/Templates/InterfaceTemplate.cs index e8d755462..420be69c1 100644 --- a/src/NJsonSchema.CodeGeneration.TypeScript/Templates/InterfaceTemplate.cs +++ b/src/NJsonSchema.CodeGeneration.TypeScript/Templates/InterfaceTemplate.cs @@ -48,7 +48,7 @@ public virtual string TransformText() this.Write("export interface "); #line 4 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.TypeScript\Templates\InterfaceTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(Model.Class)); + this.Write(this.ToStringHelper.ToStringWithCulture(Model.ClassName)); #line default #line hidden diff --git a/src/NJsonSchema.CodeGeneration.TypeScript/Templates/InterfaceTemplate.tt b/src/NJsonSchema.CodeGeneration.TypeScript/Templates/InterfaceTemplate.tt index e657627af..34882dd9c 100644 --- a/src/NJsonSchema.CodeGeneration.TypeScript/Templates/InterfaceTemplate.tt +++ b/src/NJsonSchema.CodeGeneration.TypeScript/Templates/InterfaceTemplate.tt @@ -1,7 +1,7 @@ <#@ template visibility="internal" #> <#if(Model.HasDescription){#> /** <#=Model.Description#> */ -<#}#>export interface <#=Model.Class#><#=Model.Inheritance#> { +<#}#>export interface <#=Model.ClassName#><#=Model.Inheritance#> { <#foreach(var property in Model.Properties){#> <# if(property.HasDescription){#> /** <#=property.Description#> */ diff --git a/src/NJsonSchema.CodeGeneration.TypeScript/Templates/KnockoutClassTemplate.cs b/src/NJsonSchema.CodeGeneration.TypeScript/Templates/KnockoutClassTemplate.cs index 6186eaaa7..2eeae3f42 100644 --- a/src/NJsonSchema.CodeGeneration.TypeScript/Templates/KnockoutClassTemplate.cs +++ b/src/NJsonSchema.CodeGeneration.TypeScript/Templates/KnockoutClassTemplate.cs @@ -49,7 +49,7 @@ public virtual string TransformText() this.Write("export class "); #line 4 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.TypeScript\Templates\KnockoutClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(Model.Class)); + this.Write(this.ToStringHelper.ToStringWithCulture(Model.ClassName)); #line default #line hidden @@ -328,7 +328,7 @@ public virtual string TransformText() this.Write("): "); #line 40 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.TypeScript\Templates\KnockoutClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(Model.Class)); + this.Write(this.ToStringHelper.ToStringWithCulture(Model.ClassName)); #line default #line hidden @@ -389,14 +389,14 @@ public virtual string TransformText() this.Write(" return createInstance<"); #line 47 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.TypeScript\Templates\KnockoutClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(Model.Class)); + this.Write(this.ToStringHelper.ToStringWithCulture(Model.ClassName)); #line default #line hidden this.Write(">(data, _mappings, "); #line 47 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.TypeScript\Templates\KnockoutClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(Model.Class)); + this.Write(this.ToStringHelper.ToStringWithCulture(Model.ClassName)); #line default #line hidden @@ -410,14 +410,14 @@ public virtual string TransformText() this.Write(" return createInstance<"); #line 49 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.TypeScript\Templates\KnockoutClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(Model.Class)); + this.Write(this.ToStringHelper.ToStringWithCulture(Model.ClassName)); #line default #line hidden this.Write(">(data, _mappings, "); #line 49 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.TypeScript\Templates\KnockoutClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(Model.Class)); + this.Write(this.ToStringHelper.ToStringWithCulture(Model.ClassName)); #line default #line hidden @@ -483,7 +483,7 @@ public virtual string TransformText() this.Write(" let result = new "); #line 61 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.TypeScript\Templates\KnockoutClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(Model.Class)); + this.Write(this.ToStringHelper.ToStringWithCulture(Model.ClassName)); #line default #line hidden @@ -567,7 +567,7 @@ public virtual string TransformText() this.Write("\r\n clone() {\r\n const json = this.toJSON();\r\n let result = new "); #line 85 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.TypeScript\Templates\KnockoutClassTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(Model.Class)); + this.Write(this.ToStringHelper.ToStringWithCulture(Model.ClassName)); #line default #line hidden diff --git a/src/NJsonSchema.CodeGeneration.TypeScript/Templates/KnockoutClassTemplate.tt b/src/NJsonSchema.CodeGeneration.TypeScript/Templates/KnockoutClassTemplate.tt index 8a0647c3b..bda040032 100644 --- a/src/NJsonSchema.CodeGeneration.TypeScript/Templates/KnockoutClassTemplate.tt +++ b/src/NJsonSchema.CodeGeneration.TypeScript/Templates/KnockoutClassTemplate.tt @@ -1,7 +1,7 @@ <#@ template visibility="internal" #> <#@ import namespace="NJsonSchema" #> <#if(Model.HasDescription){#>/** <#=Model.Description#> */ -<#}#>export class <#=Model.Class#><#=Model.Inheritance#> { +<#}#>export class <#=Model.ClassName#><#=Model.Inheritance#> { <#foreach (var property in Model.Properties){#> <# if(property.HasDescription){#> /** <#=property.Description#> */ @@ -37,16 +37,16 @@ } } - static fromJS(data: any<#if(Model.HandleReferences){#>, _mappings?: any<#}#>): <#=Model.Class#> { + static fromJS(data: any<#if(Model.HandleReferences){#>, _mappings?: any<#}#>): <#=Model.ClassName#> { <#if(Model.HandleReferences){#> <# if(Model.HasBaseDiscriminator){#> <# foreach (var derivedClass in Model.DerivedClasses){#> if (data["<#=Model.BaseDiscriminator#>"] === "<#=derivedClass.Key#>") return createInstance<<#=derivedClass.Value#>>(data, _mappings, <#=derivedClass.Value#>); <# }#> - return createInstance<<#=Model.Class#>>(data, _mappings, <#=Model.Class#>); + return createInstance<<#=Model.ClassName#>>(data, _mappings, <#=Model.ClassName#>); <# }else{#> - return createInstance<<#=Model.Class#>>(data, _mappings, <#=Model.Class#>); + return createInstance<<#=Model.ClassName#>>(data, _mappings, <#=Model.ClassName#>); <# }#> <#}else{#> <# if(Model.HasBaseDiscriminator){#> @@ -58,7 +58,7 @@ } <# }#> <# }#> - let result = new <#=Model.Class#>(); + let result = new <#=Model.ClassName#>(); result.init(data); return result; <#}#> @@ -82,7 +82,7 @@ clone() { const json = this.toJSON(); - let result = new <#=Model.Class#>(); + let result = new <#=Model.ClassName#>(); result.init(json); return result; } diff --git a/src/NJsonSchema.CodeGeneration.TypeScript/TypeScriptGeneratorSettings.cs b/src/NJsonSchema.CodeGeneration.TypeScript/TypeScriptGeneratorSettings.cs index ff4913743..013c68af0 100644 --- a/src/NJsonSchema.CodeGeneration.TypeScript/TypeScriptGeneratorSettings.cs +++ b/src/NJsonSchema.CodeGeneration.TypeScript/TypeScriptGeneratorSettings.cs @@ -28,7 +28,7 @@ public TypeScriptGeneratorSettings() ConvertConstructorInterfaceData = false; PropertyNameGenerator = new TypeScriptPropertyNameGenerator(); - TemplateFactory = new DefaultTemplateFactory(); + TemplateFactory = new DefaultTemplateFactory(this); } /// Gets or sets the target TypeScript version (default: 1.8). diff --git a/src/NJsonSchema.CodeGeneration/CodeGeneratorSettingsBase.cs b/src/NJsonSchema.CodeGeneration/CodeGeneratorSettingsBase.cs index 38bcc9516..9729a832a 100644 --- a/src/NJsonSchema.CodeGeneration/CodeGeneratorSettingsBase.cs +++ b/src/NJsonSchema.CodeGeneration/CodeGeneratorSettingsBase.cs @@ -44,5 +44,8 @@ public CodeGeneratorSettingsBase() /// Gets or sets the template factory. [JsonIgnore] public ITemplateFactory TemplateFactory { get; set; } + + /// Gets or sets a value indicating whether to use DotLiquid templates (experimental). + public bool UseLiquidTemplates { get; set; } } } \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration/DefaultTemplateFactory.cs b/src/NJsonSchema.CodeGeneration/DefaultTemplateFactory.cs index 9de61c96d..056c6f1ba 100644 --- a/src/NJsonSchema.CodeGeneration/DefaultTemplateFactory.cs +++ b/src/NJsonSchema.CodeGeneration/DefaultTemplateFactory.cs @@ -15,7 +15,14 @@ namespace NJsonSchema.CodeGeneration /// The default template factory which loads templates from embedded resources. public class DefaultTemplateFactory : ITemplateFactory { - public static bool UseLiquid { get; set; } = true; + private readonly CodeGeneratorSettingsBase _settings; + + /// Initializes a new instance of the class. + /// The settings. + public DefaultTemplateFactory(CodeGeneratorSettingsBase settings) + { + _settings = settings; + } /// Creates a template for the given language, template name and template model. /// Supports NJsonSchema and NSwag embedded templates. @@ -26,26 +33,39 @@ public class DefaultTemplateFactory : ITemplateFactory /// Could not load template.. public virtual ITemplate CreateTemplate(string package, string template, object model) { - var assembly = Assembly.Load(new AssemblyName("NJsonSchema.CodeGeneration." + package)); - var resourceName = "NJsonSchema.CodeGeneration." + package + ".Templates." + template + ".liquid"; - var resource = assembly.GetManifestResourceStream(resourceName); - if (resource != null && UseLiquid) + if (_settings.UseLiquidTemplates) { - using (var reader = new StreamReader(resource)) - return new LiquidTemplate(reader.ReadToEnd(), model); + var assembly = Assembly.Load(new AssemblyName("NJsonSchema.CodeGeneration." + package)); + var resourceName = "NJsonSchema.CodeGeneration." + package + ".Templates." + template + ".liquid"; + + var resource = assembly.GetManifestResourceStream(resourceName); + if (resource != null) + { + using (var reader = new StreamReader(resource)) + return new LiquidTemplate(reader.ReadToEnd(), model); + } + else + { + return CreateT4Template(package, template, model); + } } else { - var typeName = "NJsonSchema.CodeGeneration." + package + ".Templates." + template + "Template"; - var type = Type.GetType(typeName); - if (type == null) - type = Assembly.Load(new AssemblyName("NJsonSchema.CodeGeneration." + package))?.GetType(typeName); + return CreateT4Template(package, template, model); + } + } + + private ITemplate CreateT4Template(string package, string template, object model) + { + var typeName = "NJsonSchema.CodeGeneration." + package + ".Templates." + template + "Template"; + var type = Type.GetType(typeName); + if (type == null) + type = Assembly.Load(new AssemblyName("NJsonSchema.CodeGeneration." + package))?.GetType(typeName); - if (type != null) - return (ITemplate)Activator.CreateInstance(type, model); + if (type != null) + return (ITemplate) Activator.CreateInstance(type, model); - throw new InvalidOperationException("Could not load template '" + template + "'."); - } + throw new InvalidOperationException("Could not load template '" + template + "'."); } } } \ No newline at end of file From c1eb2458503ed03e14bf4d334bc54a426242315d Mon Sep 17 00:00:00 2001 From: Rico Suter Date: Tue, 10 Oct 2017 10:32:57 +0200 Subject: [PATCH 05/19] Improved template --- .../Templates/Class.liquid | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.liquid b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.liquid index a840ddb95..cd51cfdd2 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.liquid +++ b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.liquid @@ -1,12 +1,12 @@ -{% if HasDescription %} +{%- if HasDescription -%} /// {{ Description | CSharpDocs }} -{% endif %} -{% if HasDiscriminator %} +{%- endif -%} +{%- if HasDiscriminator -%} [Newtonsoft.Json.JsonConverter(typeof(JsonInheritanceConverter), "{{ Discriminator }}")] {%- for derivedClass in DerivedClasses -%} [JsonInheritanceAttribute("{{ derivedClass.Key }}", typeof({{ derivedClass.Value }}))] {%- endfor -%} -{% endif %} +{%- endif -%} [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "{{ToolchainVersion}}")] public partial class {{ClassName}} {{InheritanceCode}} { From d49df1821b23fb2d69c5f5c93943ab06f08031c2 Mon Sep 17 00:00:00 2001 From: Rico Suter Date: Thu, 12 Oct 2017 21:57:39 +0200 Subject: [PATCH 06/19] Updated liquid template --- src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.liquid | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.liquid b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.liquid index cd51cfdd2..c8b4e64da 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.liquid +++ b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.liquid @@ -8,7 +8,7 @@ {%- endfor -%} {%- endif -%} [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "{{ToolchainVersion}}")] -public partial class {{ClassName}} {{InheritanceCode}} +{{ TypeAccessModifier }} partial class {{ClassName}} {{InheritanceCode}} { {%- if RenderInpc -%} {%- for property in Properties -%} From 5bee7e3ad0d77aac3ee1caa47a4ca4620320945a Mon Sep 17 00:00:00 2001 From: Rico Suter Date: Fri, 13 Oct 2017 23:59:26 +0200 Subject: [PATCH 07/19] Clean up models --- .../CodeGeneratorSettingsBase.cs | 2 +- src/NJsonSchema.CodeGeneration/LiquidTemplate.cs | 3 +-- .../Models/ClassTemplateModelBase.cs | 2 +- .../Models/EnumerationItemModel.cs | 2 +- .../Models/PropertyModelBase.cs | 9 +-------- .../Models/TemplateModelBase.cs | 8 -------- 6 files changed, 5 insertions(+), 21 deletions(-) delete mode 100644 src/NJsonSchema.CodeGeneration/Models/TemplateModelBase.cs diff --git a/src/NJsonSchema.CodeGeneration/CodeGeneratorSettingsBase.cs b/src/NJsonSchema.CodeGeneration/CodeGeneratorSettingsBase.cs index 9729a832a..8c4329fed 100644 --- a/src/NJsonSchema.CodeGeneration/CodeGeneratorSettingsBase.cs +++ b/src/NJsonSchema.CodeGeneration/CodeGeneratorSettingsBase.cs @@ -46,6 +46,6 @@ public CodeGeneratorSettingsBase() public ITemplateFactory TemplateFactory { get; set; } /// Gets or sets a value indicating whether to use DotLiquid templates (experimental). - public bool UseLiquidTemplates { get; set; } + public bool UseLiquidTemplates { get; set; } = true; } } \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration/LiquidTemplate.cs b/src/NJsonSchema.CodeGeneration/LiquidTemplate.cs index 649d812d0..a42f1c778 100644 --- a/src/NJsonSchema.CodeGeneration/LiquidTemplate.cs +++ b/src/NJsonSchema.CodeGeneration/LiquidTemplate.cs @@ -6,7 +6,6 @@ // Rico Suter, mail@rsuter.com //----------------------------------------------------------------------- -using System.Diagnostics; using DotLiquid; namespace NJsonSchema.CodeGeneration @@ -35,7 +34,7 @@ public string Render() } } - public static class LiquidFilters + internal static class LiquidFilters { public static string CSharpDocs(string input) { diff --git a/src/NJsonSchema.CodeGeneration/Models/ClassTemplateModelBase.cs b/src/NJsonSchema.CodeGeneration/Models/ClassTemplateModelBase.cs index 6d3af3004..a18d04750 100644 --- a/src/NJsonSchema.CodeGeneration/Models/ClassTemplateModelBase.cs +++ b/src/NJsonSchema.CodeGeneration/Models/ClassTemplateModelBase.cs @@ -12,7 +12,7 @@ namespace NJsonSchema.CodeGeneration.Models { /// The class template base class. - public abstract class ClassTemplateModelBase : TemplateModelBase + public abstract class ClassTemplateModelBase { private readonly JsonSchema4 _schema; private readonly object _rootObject; diff --git a/src/NJsonSchema.CodeGeneration/Models/EnumerationItemModel.cs b/src/NJsonSchema.CodeGeneration/Models/EnumerationItemModel.cs index 9e28746d9..25257c4a9 100644 --- a/src/NJsonSchema.CodeGeneration/Models/EnumerationItemModel.cs +++ b/src/NJsonSchema.CodeGeneration/Models/EnumerationItemModel.cs @@ -9,7 +9,7 @@ namespace NJsonSchema.CodeGeneration.Models { /// Describes an enumeration entry. - public class EnumerationItemModel : TemplateModelBase + public class EnumerationItemModel { /// Gets or sets the name. public string Name { get; set; } diff --git a/src/NJsonSchema.CodeGeneration/Models/PropertyModelBase.cs b/src/NJsonSchema.CodeGeneration/Models/PropertyModelBase.cs index 2649b6ff3..8faad16d6 100644 --- a/src/NJsonSchema.CodeGeneration/Models/PropertyModelBase.cs +++ b/src/NJsonSchema.CodeGeneration/Models/PropertyModelBase.cs @@ -7,12 +7,11 @@ //----------------------------------------------------------------------- using System; -using DotLiquid; namespace NJsonSchema.CodeGeneration.Models { /// The property template model base class. - public abstract class PropertyModelBase : TemplateModelBase + public abstract class PropertyModelBase { private readonly ClassTemplateModelBase _classTemplateModel; private readonly JsonProperty _property; @@ -76,11 +75,5 @@ protected string GetTypeNameHint() return className + ConversionUtilities.ConvertToUpperCamelCase(PropertyName, false); } - - public object ToLiquid() - { - var x = Hash.FromAnonymousObject(this); - return x; - } } } \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration/Models/TemplateModelBase.cs b/src/NJsonSchema.CodeGeneration/Models/TemplateModelBase.cs deleted file mode 100644 index cd2822c54..000000000 --- a/src/NJsonSchema.CodeGeneration/Models/TemplateModelBase.cs +++ /dev/null @@ -1,8 +0,0 @@ -using DotLiquid; - -namespace NJsonSchema.CodeGeneration.Models -{ - public class TemplateModelBase - { - } -} From 8322a634761be06cde154aea5389628d1c35113a Mon Sep 17 00:00:00 2001 From: Rico Suter Date: Sat, 14 Oct 2017 19:57:46 +0200 Subject: [PATCH 08/19] Added CodeArtifactCollection, #514 --- .../CSharpGenerator.cs | 19 ++++--- .../CSharpTypeResolver.cs | 39 ++++++++++----- .../TypeScript/ClassOrderTests.cs | 16 +++--- .../TypeScriptGenerator.cs | 20 +++++--- .../TypeScriptTypeResolver.cs | 28 +++++++++++ ...TypeGeneratorResult.cs => CodeArtifact.cs} | 50 +++++++++++-------- ...Utilities.cs => CodeArtifactCollection.cs} | 31 +++++++++--- .../CodeArtifactLanguage.cs | 23 +++++++++ .../CodeArtifactType.cs | 26 ++++++++++ .../ITemplateFactory.cs | 4 +- .../TypeGeneratorBase.cs | 2 +- .../TypeResolverBase.cs | 30 +++-------- 12 files changed, 199 insertions(+), 89 deletions(-) rename src/NJsonSchema.CodeGeneration/{TypeGeneratorResult.cs => CodeArtifact.cs} (76%) rename src/NJsonSchema.CodeGeneration/{ClassOrderUtilities.cs => CodeArtifactCollection.cs} (59%) create mode 100644 src/NJsonSchema.CodeGeneration/CodeArtifactLanguage.cs create mode 100644 src/NJsonSchema.CodeGeneration/CodeArtifactType.cs diff --git a/src/NJsonSchema.CodeGeneration.CSharp/CSharpGenerator.cs b/src/NJsonSchema.CodeGeneration.CSharp/CSharpGenerator.cs index cb01ad0ba..32b6d9fc3 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/CSharpGenerator.cs +++ b/src/NJsonSchema.CodeGeneration.CSharp/CSharpGenerator.cs @@ -59,7 +59,7 @@ public override string GenerateFile(string rootTypeNameHint) var model = new FileTemplateModel { Namespace = Settings.Namespace ?? string.Empty, - Classes = ConversionUtilities.TrimWhiteSpaces(_resolver.GenerateClasses()) + Classes = ConversionUtilities.TrimWhiteSpaces(_resolver.GenerateTypes().Concatenate()) }; var template = Settings.TemplateFactory.CreateTemplate("CSharp", "File", model); @@ -69,7 +69,7 @@ public override string GenerateFile(string rootTypeNameHint) /// Generates the type. /// The type name hint. /// The code. - public override TypeGeneratorResult GenerateType(string typeNameHint) + public override CodeArtifact GenerateType(string typeNameHint) { var typeName = _resolver.GetOrGenerateTypeName(_schema, typeNameHint); @@ -79,17 +79,21 @@ public override TypeGeneratorResult GenerateType(string typeNameHint) return GenerateClass(typeName); } - private TypeGeneratorResult GenerateClass(string typeName) + private CodeArtifact GenerateClass(string typeName) { var model = new ClassTemplateModel(typeName, Settings, _resolver, _schema, RootObject); RenamePropertyWithSameNameAsClass(typeName, model.Properties); var template = Settings.TemplateFactory.CreateTemplate("CSharp", "Class", model); - return new TypeGeneratorResult + return new CodeArtifact { + Type = CodeArtifactType.Class, + Language = CodeArtifactLanguage.CSharp, + TypeName = typeName, BaseTypeName = model.BaseClassName, + Code = template.Render() }; } @@ -107,12 +111,15 @@ private void RenamePropertyWithSameNameAsClass(string typeName, IEnumerableRico Suter, mail@rsuter.com //----------------------------------------------------------------------- +using System.Collections.Generic; using System.Linq; using NJsonSchema.CodeGeneration.CSharp.Models; using NJsonSchema.CodeGeneration.CSharp.Templates; @@ -77,26 +78,38 @@ public override string Resolve(JsonSchema4 schema, bool isNullable, string typeN return AddGenerator(schema, typeNameHint); } - /// Generates all necessary classes. + /// Generates the code for all described types (e.g. interfaces, classes, enums, etc). /// The code. - public string GenerateClasses() + public override CodeArtifactCollection GenerateTypes() { - var classes = GenerateTypes(null); + var collection = base.GenerateTypes(); + var results = new List(); - // TODO: Move utility classes to FileTemplate.tt - if (classes.Contains("JsonInheritanceConverter")) + if (collection.Artifacts.Any(r => r.Code.Contains("JsonInheritanceConverter"))) { - var templateModel = new JsonInheritanceConverterTemplateModel(Settings); - var template = new JsonInheritanceConverterTemplate(templateModel); - classes += "\n\n" + template.Render(); + results.Add(new CodeArtifact + { + Type = CodeArtifactType.Class, + Language = CodeArtifactLanguage.CSharp, + + TypeName = "JsonInheritanceConverter", + Code = new JsonInheritanceConverterTemplate(new JsonInheritanceConverterTemplateModel(Settings)).Render() + }); } - if (classes.Contains("DateFormatConverter")) + + if (collection.Artifacts.Any(r => r.Code.Contains("DateFormatConverter"))) { - var templateModel = new DateFormatConverterTemplateModel(Settings); - var template = new DateFormatConverterTemplate(templateModel); - classes += "\n\n" + template.Render(); + results.Add(new CodeArtifact + { + Type = CodeArtifactType.Class, + Language = CodeArtifactLanguage.CSharp, + + TypeName = "DateFormatConverter", + Code = new DateFormatConverterTemplate(new DateFormatConverterTemplateModel(Settings)).Render() + }); } - return classes; + + return new CodeArtifactCollection(collection.Artifacts.Concat(results)); } /// Adds a generator for the given schema if necessary. diff --git a/src/NJsonSchema.CodeGeneration.Tests/TypeScript/ClassOrderTests.cs b/src/NJsonSchema.CodeGeneration.Tests/TypeScript/ClassOrderTests.cs index 53ec56fd5..ff4f92ca5 100644 --- a/src/NJsonSchema.CodeGeneration.Tests/TypeScript/ClassOrderTests.cs +++ b/src/NJsonSchema.CodeGeneration.Tests/TypeScript/ClassOrderTests.cs @@ -11,39 +11,39 @@ public class ClassOrderTests public void When_class_order_is_wrong_then_classes_are_correctly_reordered() { //// Arrange - var classes = new List + var classes = new List { - new TypeGeneratorResult + new CodeArtifact { TypeName = "Car" }, - new TypeGeneratorResult + new CodeArtifact { TypeName = "Apple", BaseTypeName = "Fruit" }, - new TypeGeneratorResult + new CodeArtifact { TypeName = "Professor", BaseTypeName = "Teacher" }, - new TypeGeneratorResult + new CodeArtifact { TypeName = "Teacher", BaseTypeName = "Person" }, - new TypeGeneratorResult + new CodeArtifact { TypeName = "Fruit" }, - new TypeGeneratorResult + new CodeArtifact { TypeName = "Person" } }; //// Act - classes = ClassOrderUtilities.Order(classes).ToList(); + classes = CodeArtifactCollection.OrderByBaseDependency(classes).ToList(); var order = string.Join(", ", classes.Select(c => c.TypeName)); //// Assert diff --git a/src/NJsonSchema.CodeGeneration.TypeScript/TypeScriptGenerator.cs b/src/NJsonSchema.CodeGeneration.TypeScript/TypeScriptGenerator.cs index d090ba80c..d2208ec18 100644 --- a/src/NJsonSchema.CodeGeneration.TypeScript/TypeScriptGenerator.cs +++ b/src/NJsonSchema.CodeGeneration.TypeScript/TypeScriptGenerator.cs @@ -36,7 +36,7 @@ public TypeScriptGenerator(JsonSchema4 schema, TypeScriptGeneratorSettings setti /// The generator settings. /// The resolver. /// The root object to search for all JSON Schemas. - public TypeScriptGenerator(JsonSchema4 schema, TypeScriptGeneratorSettings settings, TypeScriptTypeResolver resolver, object rootObject) + public TypeScriptGenerator(JsonSchema4 schema, TypeScriptGeneratorSettings settings, TypeScriptTypeResolver resolver, object rootObject) : base(schema, rootObject) { _schema = schema; @@ -54,10 +54,10 @@ public override string GenerateFile(string rootTypeNameHint) { _resolver.Resolve(_schema, false, rootTypeNameHint); // register root type - var extensionCode = new TypeScriptExtensionCode(Settings.ExtensionCode, Settings.ExtendedClasses); + var extensionCode = new TypeScriptExtensionCode(Settings.ExtensionCode, Settings.ExtendedClasses); var model = new FileTemplateModel(Settings) { - Types = ConversionUtilities.TrimWhiteSpaces(_resolver.GenerateTypes(extensionCode)), + Types = ConversionUtilities.TrimWhiteSpaces(_resolver.GenerateTypes().Concatenate()), ExtensionCode = extensionCode }; @@ -68,7 +68,7 @@ public override string GenerateFile(string rootTypeNameHint) /// Generates the type. /// The fallback type name. /// The code. - public override TypeGeneratorResult GenerateType(string typeNameHint) + public override CodeArtifact GenerateType(string typeNameHint) { var typeName = _resolver.GetOrGenerateTypeName(_schema, typeNameHint); @@ -76,8 +76,11 @@ public override TypeGeneratorResult GenerateType(string typeNameHint) { var model = new EnumTemplateModel(typeName, _schema, Settings); var template = Settings.TemplateFactory.CreateTemplate("TypeScript", "Enum", model); - return new TypeGeneratorResult + return new CodeArtifact { + Type = CodeArtifactType.Enum, + Language = CodeArtifactLanguage.TypeScript, + TypeName = typeName, Code = template.Render() }; @@ -86,10 +89,15 @@ public override TypeGeneratorResult GenerateType(string typeNameHint) { var model = new ClassTemplateModel(typeName, typeNameHint, Settings, _resolver, _schema, RootObject); var template = Settings.CreateTemplate(typeName, model); - return new TypeGeneratorResult + return new CodeArtifact { + Type = Settings.TypeStyle == TypeScriptTypeStyle.Interface ? + CodeArtifactType.Interface : CodeArtifactType.Class, + Language = CodeArtifactLanguage.TypeScript, + TypeName = typeName, BaseTypeName = model.BaseClass, + Code = template.Render() }; } diff --git a/src/NJsonSchema.CodeGeneration.TypeScript/TypeScriptTypeResolver.cs b/src/NJsonSchema.CodeGeneration.TypeScript/TypeScriptTypeResolver.cs index f763ab933..a48559130 100644 --- a/src/NJsonSchema.CodeGeneration.TypeScript/TypeScriptTypeResolver.cs +++ b/src/NJsonSchema.CodeGeneration.TypeScript/TypeScriptTypeResolver.cs @@ -62,6 +62,34 @@ protected override TypeScriptGenerator CreateTypeGenerator(JsonSchema4 schema) return new TypeScriptGenerator(schema, Settings, this, _rootObject); } + /// + public override CodeArtifactCollection GenerateTypes() + { + var extensionCode = new TypeScriptExtensionCode(Settings.ExtensionCode, Settings.ExtendedClasses); + + var collection = base.GenerateTypes(); + foreach (var artifact in collection.Artifacts) + { + if (extensionCode.ExtensionClasses.ContainsKey(artifact.TypeName) == true) + { + var classCode = artifact.Code; + + var index = classCode.IndexOf("constructor(", StringComparison.Ordinal); + if (index != -1) + artifact.Code = classCode.Insert(index, extensionCode.GetExtensionClassBody(artifact.TypeName).Trim() + "\n\n "); + else + { + index = classCode.IndexOf("class", StringComparison.Ordinal); + index = classCode.IndexOf("{", index, StringComparison.Ordinal) + 1; + + artifact.Code = classCode.Insert(index, "\n " + extensionCode.GetExtensionClassBody(artifact.TypeName).Trim() + "\n"); + } + } + } + + return collection; + } + private string Resolve(JsonSchema4 schema, string typeNameHint, bool addInterfacePrefix) { if (schema == null) diff --git a/src/NJsonSchema.CodeGeneration/TypeGeneratorResult.cs b/src/NJsonSchema.CodeGeneration/CodeArtifact.cs similarity index 76% rename from src/NJsonSchema.CodeGeneration/TypeGeneratorResult.cs rename to src/NJsonSchema.CodeGeneration/CodeArtifact.cs index c1e5e6e3d..e81eb2fbb 100644 --- a/src/NJsonSchema.CodeGeneration/TypeGeneratorResult.cs +++ b/src/NJsonSchema.CodeGeneration/CodeArtifact.cs @@ -1,23 +1,29 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Rico Suter. All rights reserved. -// -// https://github.com/rsuter/NJsonSchema/blob/master/LICENSE.md -// Rico Suter, mail@rsuter.com -//----------------------------------------------------------------------- - -namespace NJsonSchema.CodeGeneration -{ - /// The type generator result. - public class TypeGeneratorResult - { - /// Gets or sets the type name. - public string TypeName { get; set; } - - /// Gets or sets the name of the base type (i.e. the name of the inherited class). - public string BaseTypeName { get; set; } - - /// Gets or sets the generated code. - public string Code { get; set; } - } +//----------------------------------------------------------------------- +// +// Copyright (c) Rico Suter. All rights reserved. +// +// https://github.com/rsuter/NJsonSchema/blob/master/LICENSE.md +// Rico Suter, mail@rsuter.com +//----------------------------------------------------------------------- + +namespace NJsonSchema.CodeGeneration +{ + /// The type generator result. + public class CodeArtifact + { + /// Gets or sets the type name. + public string TypeName { get; set; } + + /// Gets or sets the name of the base type (i.e. the name of the inherited class). + public string BaseTypeName { get; set; } + + /// Gets or sets the generated code. + public string Code { get; set; } + + /// Gets or sets the artifact type. + public CodeArtifactType Type { get; set; } + + /// Get or sets the artifact language. + public CodeArtifactLanguage Language { get; set; } + } } \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration/ClassOrderUtilities.cs b/src/NJsonSchema.CodeGeneration/CodeArtifactCollection.cs similarity index 59% rename from src/NJsonSchema.CodeGeneration/ClassOrderUtilities.cs rename to src/NJsonSchema.CodeGeneration/CodeArtifactCollection.cs index 8132bcc9c..f7422cd89 100644 --- a/src/NJsonSchema.CodeGeneration/ClassOrderUtilities.cs +++ b/src/NJsonSchema.CodeGeneration/CodeArtifactCollection.cs @@ -1,5 +1,5 @@ -//----------------------------------------------------------------------- -// +//----------------------------------------------------------------------- +// // Copyright (c) Rico Suter. All rights reserved. // // https://github.com/rsuter/NJsonSchema/blob/master/LICENSE.md @@ -11,15 +11,32 @@ namespace NJsonSchema.CodeGeneration { - /// Utility class to order classes. - public static class ClassOrderUtilities + /// + public class CodeArtifactCollection { + /// Initializes a new instance of the class. + /// The artifacts. + public CodeArtifactCollection(IEnumerable artifacts) + { + Artifacts = OrderByBaseDependency(artifacts); + } + + /// Gets the artifacts. + public IEnumerable Artifacts { get; } + + /// Concatenates the results. + /// The result. + public string Concatenate() + { + return string.Join("\n\n", Artifacts.Select(p => p.Code)); + } + /// Reorders the results so that base classes are always before child classes. /// The results. /// The reordered results. - public static IEnumerable Order(IEnumerable results) + public static IEnumerable OrderByBaseDependency(IEnumerable results) { - var newResults = new List(results); + var newResults = new List(results); foreach (var result in newResults.ToArray()) { if (!string.IsNullOrEmpty(result.BaseTypeName)) @@ -44,4 +61,4 @@ public static IEnumerable Order(IEnumerable +// Copyright (c) Rico Suter. All rights reserved. +// +// https://github.com/rsuter/NJsonSchema/blob/master/LICENSE.md +// Rico Suter, mail@rsuter.com +//----------------------------------------------------------------------- + +namespace NJsonSchema.CodeGeneration +{ + /// The code artifact type. + public enum CodeArtifactLanguage + { + /// Undefined. + Undefined, + + /// A class. + CSharp, + + /// A class. + TypeScript + } +} \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration/CodeArtifactType.cs b/src/NJsonSchema.CodeGeneration/CodeArtifactType.cs new file mode 100644 index 000000000..1673890b9 --- /dev/null +++ b/src/NJsonSchema.CodeGeneration/CodeArtifactType.cs @@ -0,0 +1,26 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Rico Suter. All rights reserved. +// +// https://github.com/rsuter/NJsonSchema/blob/master/LICENSE.md +// Rico Suter, mail@rsuter.com +//----------------------------------------------------------------------- + +namespace NJsonSchema.CodeGeneration +{ + /// The code artifact type. + public enum CodeArtifactType + { + /// Undefined. + Undefined, + + /// A class (e.g. C# or TypeScript class). + Class, + + /// An interface (e.g. C# or TypeScript interface). + Interface, + + /// An enum (e.g. C# or TypeScript interface). + Enum + } +} \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration/ITemplateFactory.cs b/src/NJsonSchema.CodeGeneration/ITemplateFactory.cs index 699c71544..af4398b2e 100644 --- a/src/NJsonSchema.CodeGeneration/ITemplateFactory.cs +++ b/src/NJsonSchema.CodeGeneration/ITemplateFactory.cs @@ -12,10 +12,10 @@ namespace NJsonSchema.CodeGeneration public interface ITemplateFactory { /// Creates a template for the given language, template name and template model. - /// The package name (i.e. the language, 'CSharp' or 'TypeScript'). + /// The language (i.e. 'CSharp' or 'TypeScript'). /// The template name. /// The template model. /// The template. - ITemplate CreateTemplate(string package, string template, object model); + ITemplate CreateTemplate(string language, string template, object model); } } \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration/TypeGeneratorBase.cs b/src/NJsonSchema.CodeGeneration/TypeGeneratorBase.cs index 34148d9fd..093023ea3 100644 --- a/src/NJsonSchema.CodeGeneration/TypeGeneratorBase.cs +++ b/src/NJsonSchema.CodeGeneration/TypeGeneratorBase.cs @@ -14,7 +14,7 @@ public abstract class TypeGeneratorBase : GeneratorBase /// Generates the type. /// The type name hint. /// The code. - public abstract TypeGeneratorResult GenerateType(string typeNameHint); + public abstract CodeArtifact GenerateType(string typeNameHint); /// Initializes a new instance of the class. /// The schema. diff --git a/src/NJsonSchema.CodeGeneration/TypeResolverBase.cs b/src/NJsonSchema.CodeGeneration/TypeResolverBase.cs index 10e68994a..a9f81e9af 100644 --- a/src/NJsonSchema.CodeGeneration/TypeResolverBase.cs +++ b/src/NJsonSchema.CodeGeneration/TypeResolverBase.cs @@ -37,10 +37,10 @@ public string TryResolve(JsonSchema4 schema, string typeNameHint) /// Generates the code for all described types (e.g. interfaces, classes, enums, etc). /// The code. - public string GenerateTypes(ExtensionCode extensionCode) + public virtual CodeArtifactCollection GenerateTypes() { var processedTypes = new List(); - var types = new Dictionary(); + var types = new Dictionary(); while (_types.Any(t => !processedTypes.Contains(t.Key))) { foreach (var pair in _types.ToList()) @@ -51,28 +51,10 @@ public string GenerateTypes(ExtensionCode extensionCode) } } - return string.Join("\n\n", ClassOrderUtilities.Order(types.Values) - .Where(p => !_settings.ExcludedTypeNames.Contains(p.TypeName)) - .Select(p => - { - if (extensionCode?.ExtensionClasses.ContainsKey(p.TypeName) == true) - { - var classCode = p.Code; - - var index = classCode.IndexOf("constructor("); - if (index != -1) - return classCode.Insert(index, extensionCode.GetExtensionClassBody(p.TypeName).Trim() + "\n\n "); - else - { - index = classCode.IndexOf("class"); - index = classCode.IndexOf("{", index) + 1; - - return classCode.Insert(index, "\n " + extensionCode.GetExtensionClassBody(p.TypeName).Trim() + "\n"); - } - } - - return p.Code; - })); + var artifacts = types.Values.Where(p => + !_settings.ExcludedTypeNames.Contains(p.TypeName)); + + return new CodeArtifactCollection(artifacts); } /// Resolves and possibly generates the specified schema. From be851d180f3ac7ede48c8c6195d08a38e4a4b194 Mon Sep 17 00:00:00 2001 From: Rico Suter Date: Sun, 15 Oct 2017 17:31:19 +0200 Subject: [PATCH 09/19] Added TemplateDirectory --- .../NJsonSchema.CodeGeneration.CSharp.csproj | 4 ++ .../Templates/Class.ToJson.liquid | 4 ++ .../Templates/Class.liquid | 5 +- .../CodeGeneratorSettingsBase.cs | 3 + .../DefaultTemplateFactory.cs | 68 +++++++++++++------ .../LiquidTemplate.cs | 65 ++++++++++++++++-- 6 files changed, 116 insertions(+), 33 deletions(-) create mode 100644 src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.ToJson.liquid diff --git a/src/NJsonSchema.CodeGeneration.CSharp/NJsonSchema.CodeGeneration.CSharp.csproj b/src/NJsonSchema.CodeGeneration.CSharp/NJsonSchema.CodeGeneration.CSharp.csproj index 38661b816..3f53ad4f9 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/NJsonSchema.CodeGeneration.CSharp.csproj +++ b/src/NJsonSchema.CodeGeneration.CSharp/NJsonSchema.CodeGeneration.CSharp.csproj @@ -20,8 +20,12 @@ true + + + + diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.ToJson.liquid b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.ToJson.liquid new file mode 100644 index 000000000..7505b8588 --- /dev/null +++ b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.ToJson.liquid @@ -0,0 +1,4 @@ +public string ToJson() +{ + return Newtonsoft.Json.JsonConvert.SerializeObject(this{{ JsonSerializerParameterCode }}); +} \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.liquid b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.liquid index c8b4e64da..589579931 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.liquid +++ b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.liquid @@ -71,10 +71,7 @@ public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; {%- endif -%} - public string ToJson() - { - return Newtonsoft.Json.JsonConvert.SerializeObject(this{{ JsonSerializerParameterCode }}); - } + {% template ToJson %} public static {{ ClassName }} FromJson(string data) { diff --git a/src/NJsonSchema.CodeGeneration/CodeGeneratorSettingsBase.cs b/src/NJsonSchema.CodeGeneration/CodeGeneratorSettingsBase.cs index 8c4329fed..b6ea76f77 100644 --- a/src/NJsonSchema.CodeGeneration/CodeGeneratorSettingsBase.cs +++ b/src/NJsonSchema.CodeGeneration/CodeGeneratorSettingsBase.cs @@ -47,5 +47,8 @@ public CodeGeneratorSettingsBase() /// Gets or sets a value indicating whether to use DotLiquid templates (experimental). public bool UseLiquidTemplates { get; set; } = true; + + /// Gets or sets the template directory path. + public string TemplateDirectory { get; set; } } } \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration/DefaultTemplateFactory.cs b/src/NJsonSchema.CodeGeneration/DefaultTemplateFactory.cs index 056c6f1ba..db66c9ea6 100644 --- a/src/NJsonSchema.CodeGeneration/DefaultTemplateFactory.cs +++ b/src/NJsonSchema.CodeGeneration/DefaultTemplateFactory.cs @@ -26,46 +26,70 @@ public DefaultTemplateFactory(CodeGeneratorSettingsBase settings) /// Creates a template for the given language, template name and template model. /// Supports NJsonSchema and NSwag embedded templates. - /// The package name (i.e. language). + /// The language. /// The template name. /// The template model. /// The template. /// Could not load template.. - public virtual ITemplate CreateTemplate(string package, string template, object model) + public virtual ITemplate CreateTemplate(string language, string template, object model) { - if (_settings.UseLiquidTemplates) + var liquidTemplate = TryGetLiquidTemplate(language, template); + if (liquidTemplate != null) + return new LiquidTemplate(language, template, liquidTemplate, model, _settings); + else + return CreateT4Template(language, template, model); + } + + /// Tries to load a Liquid template from an embedded resource. + /// The language. + /// The template name. + /// The template. + protected virtual string TryLoadEmbeddedLiquidTemplate(string language, string template) + { + var assembly = Assembly.Load(new AssemblyName("NJsonSchema.CodeGeneration." + language)); + var resourceName = "NJsonSchema.CodeGeneration." + language + ".Templates." + template + ".liquid"; + + var resource = assembly.GetManifestResourceStream(resourceName); + if (resource != null) { - var assembly = Assembly.Load(new AssemblyName("NJsonSchema.CodeGeneration." + package)); - var resourceName = "NJsonSchema.CodeGeneration." + package + ".Templates." + template + ".liquid"; + using (var reader = new StreamReader(resource)) + return reader.ReadToEnd(); + } - var resource = assembly.GetManifestResourceStream(resourceName); - if (resource != null) - { - using (var reader = new StreamReader(resource)) - return new LiquidTemplate(reader.ReadToEnd(), model); - } - else + return null; + } + + private string TryGetLiquidTemplate(string language, string template) + { + if (_settings.UseLiquidTemplates) + { + if (!template.EndsWith("!") && + !string.IsNullOrEmpty(_settings.TemplateDirectory) && + Directory.Exists(_settings.TemplateDirectory)) { - return CreateT4Template(package, template, model); + var templateFilePath = Path.Combine(_settings.TemplateDirectory, language, template + ".liquid"); + if (File.Exists(templateFilePath)) + return File.ReadAllText(templateFilePath); } + + return TryLoadEmbeddedLiquidTemplate(language, template.TrimEnd('!')); } - else - { - return CreateT4Template(package, template, model); - } + + return null; } - private ITemplate CreateT4Template(string package, string template, object model) + /// Could not load template.. + private ITemplate CreateT4Template(string language, string template, object model) { - var typeName = "NJsonSchema.CodeGeneration." + package + ".Templates." + template + "Template"; + var typeName = "NJsonSchema.CodeGeneration." + language + ".Templates." + template + "Template"; var type = Type.GetType(typeName); if (type == null) - type = Assembly.Load(new AssemblyName("NJsonSchema.CodeGeneration." + package))?.GetType(typeName); + type = Assembly.Load(new AssemblyName("NJsonSchema.CodeGeneration." + language))?.GetType(typeName); if (type != null) - return (ITemplate) Activator.CreateInstance(type, model); + return (ITemplate)Activator.CreateInstance(type, model); - throw new InvalidOperationException("Could not load template '" + template + "'."); + throw new InvalidOperationException("Could not load template '" + template + "' for language '" + language + "'."); } } } \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration/LiquidTemplate.cs b/src/NJsonSchema.CodeGeneration/LiquidTemplate.cs index a42f1c778..e46b4473e 100644 --- a/src/NJsonSchema.CodeGeneration/LiquidTemplate.cs +++ b/src/NJsonSchema.CodeGeneration/LiquidTemplate.cs @@ -6,31 +6,50 @@ // Rico Suter, mail@rsuter.com //----------------------------------------------------------------------- +using System.Collections.Generic; +using System.IO; +using System.Text.RegularExpressions; using DotLiquid; namespace NJsonSchema.CodeGeneration { internal class LiquidTemplate : ITemplate { + private readonly string _language; private readonly string _template; + private readonly string _data; private readonly object _model; + private readonly CodeGeneratorSettingsBase _settings; - public LiquidTemplate(string template, object model) + public LiquidTemplate(string language, string template, string data, object model, CodeGeneratorSettingsBase settings) { + _language = language; _template = template; + _data = data; _model = model; + _settings = settings; } public string Render() { - var tpl = Template.Parse(_template); - var hash = LiquidHash.FromObject(_model); - // TODO: Check models here - return tpl.Render(new RenderParameters + Template.RegisterTag("template"); + + var data = Regex.Replace(_data, "(\n(( )*?)\\{% template .*?) %}", m => + m.Groups[1].Value + " " + m.Groups[2].Value.Length / 4 + " %}", + RegexOptions.Singleline); + + var template = Template.Parse(data); + + var hash = _model is Hash ? (Hash)_model : LiquidHash.FromObject(_model); + hash[TemplateTag.LanguageKey] = _language; + hash[TemplateTag.TemplateKey] = _template; + hash[TemplateTag.SettingsKey] = _settings; + + return template.Render(new RenderParameters { LocalVariables = hash, Filters = new[] { typeof(LiquidFilters) } - }).Trim('\r', '\n', ' '); + }); } } @@ -38,8 +57,40 @@ internal static class LiquidFilters { public static string CSharpDocs(string input) { - // TODO: Check if this is really called! return ConversionUtilities.ConvertCSharpDocBreaks(input, 0); } } + + internal class TemplateTag : Tag + { + public static string LanguageKey = "__language"; + public static string TemplateKey = "__template"; + public static string SettingsKey = "__settings"; + + private string _template; + private int _tab; + + public override void Initialize(string tagName, string markup, List tokens) + { + var parts = markup.Trim().Split(' '); + _template = parts[0]; + _tab = parts.Length == 2 ? int.Parse(parts[1]) : 0; + base.Initialize(tagName, markup, tokens); + } + + public override void Render(Context context, TextWriter result) + { + var hash = new Hash(); + foreach (var environment in context.Environments) + hash.Merge(environment); + + var settings = (CodeGeneratorSettingsBase)hash[SettingsKey]; + var template = settings.TemplateFactory.CreateTemplate( + (string)hash[LanguageKey], + !string.IsNullOrEmpty(_template) ? (string)hash[TemplateKey] + "." + _template : (string)hash[TemplateKey] + "!", + hash); + + result.Write(ConversionUtilities.Tab(template.Render(), _tab)); + } + } } \ No newline at end of file From 9965416a21afe363a1719ca4244a55029392c564 Mon Sep 17 00:00:00 2001 From: Rico Suter Date: Sun, 15 Oct 2017 21:46:06 +0200 Subject: [PATCH 10/19] Added CSharp enum liquid template --- .../CSharpGenerator.cs | 2 +- .../Models/ClassTemplateModel.cs | 13 ++++++++----- .../Models/EnumTemplateModel.cs | 5 +++-- .../Models/FileTemplateModel.cs | 4 ++-- .../NJsonSchema.CodeGeneration.CSharp.csproj | 6 ++++-- .../Templates/FileTemplate.cs | 2 +- .../Templates/FileTemplate.tt | 2 +- .../Templates/{ => Liquid}/Class.ToJson.liquid | 0 .../Templates/{ => Liquid}/Class.liquid | 0 .../Templates/Liquid/Enum.liquid | 14 ++++++++++++++ .../CodeGeneratorSettingsBase.cs | 6 +++++- .../DefaultTemplateFactory.cs | 2 +- .../Models/ClassTemplateModelBase.cs | 2 +- .../Models/TemplateModelBase.cs | 17 +++++++++++++++++ 14 files changed, 58 insertions(+), 17 deletions(-) rename src/NJsonSchema.CodeGeneration.CSharp/Templates/{ => Liquid}/Class.ToJson.liquid (100%) rename src/NJsonSchema.CodeGeneration.CSharp/Templates/{ => Liquid}/Class.liquid (100%) create mode 100644 src/NJsonSchema.CodeGeneration.CSharp/Templates/Liquid/Enum.liquid create mode 100644 src/NJsonSchema.CodeGeneration/Models/TemplateModelBase.cs diff --git a/src/NJsonSchema.CodeGeneration.CSharp/CSharpGenerator.cs b/src/NJsonSchema.CodeGeneration.CSharp/CSharpGenerator.cs index 32b6d9fc3..d52e33aa2 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/CSharpGenerator.cs +++ b/src/NJsonSchema.CodeGeneration.CSharp/CSharpGenerator.cs @@ -59,7 +59,7 @@ public override string GenerateFile(string rootTypeNameHint) var model = new FileTemplateModel { Namespace = Settings.Namespace ?? string.Empty, - Classes = ConversionUtilities.TrimWhiteSpaces(_resolver.GenerateTypes().Concatenate()) + TypesCode = ConversionUtilities.TrimWhiteSpaces(_resolver.GenerateTypes().Concatenate()) }; var template = Settings.TemplateFactory.CreateTemplate("CSharp", "File", model); diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Models/ClassTemplateModel.cs b/src/NJsonSchema.CodeGeneration.CSharp/Models/ClassTemplateModel.cs index 301e0196b..13ddfeb82 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/Models/ClassTemplateModel.cs +++ b/src/NJsonSchema.CodeGeneration.CSharp/Models/ClassTemplateModel.cs @@ -40,9 +40,6 @@ public ClassTemplateModel(string typeName, CSharpGeneratorSettings settings, .ToList(); } - /// Gets the NJsonSchema toolchain version. - public string ToolchainVersion => JsonSchema4.ToolchainVersion; - /// Gets or sets the class name. public override string ClassName { get; } @@ -95,9 +92,15 @@ public string InheritanceCode get { if (HasInheritance) - return ": " + BaseClassName + (_settings.ClassStyle == CSharpClassStyle.Inpc ? ", System.ComponentModel.INotifyPropertyChanged" : ""); + { + return ": " + BaseClassName + (_settings.ClassStyle == CSharpClassStyle.Inpc ? + ", System.ComponentModel.INotifyPropertyChanged" : ""); + } else - return _settings.ClassStyle == CSharpClassStyle.Inpc ? ": System.ComponentModel.INotifyPropertyChanged" : ""; + { + return _settings.ClassStyle == CSharpClassStyle.Inpc ? + ": System.ComponentModel.INotifyPropertyChanged" : ""; + } } } } diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Models/EnumTemplateModel.cs b/src/NJsonSchema.CodeGeneration.CSharp/Models/EnumTemplateModel.cs index 385bb4e28..77801df39 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/Models/EnumTemplateModel.cs +++ b/src/NJsonSchema.CodeGeneration.CSharp/Models/EnumTemplateModel.cs @@ -15,7 +15,7 @@ namespace NJsonSchema.CodeGeneration.CSharp.Models // TODO: Add base class for CSharp.EnumTemplateModel and TypeScript.EnumTemplateModel /// The CSharp enum template model. - public class EnumTemplateModel + public class EnumTemplateModel : TemplateModelBase { private readonly JsonSchema4 _schema; private readonly CSharpGeneratorSettings _settings; @@ -52,7 +52,7 @@ public IEnumerable Enums get { var entries = new List(); - for (int i = 0; i < _schema.Enumeration.Count; i++) + for (var i = 0; i < _schema.Enumeration.Count; i++) { var value = _schema.Enumeration.ElementAt(i); if (value != null) @@ -69,6 +69,7 @@ public IEnumerable Enums }); } } + return entries; } } diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Models/FileTemplateModel.cs b/src/NJsonSchema.CodeGeneration.CSharp/Models/FileTemplateModel.cs index c0f60cb59..070dff80f 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/Models/FileTemplateModel.cs +++ b/src/NJsonSchema.CodeGeneration.CSharp/Models/FileTemplateModel.cs @@ -14,7 +14,7 @@ public class FileTemplateModel /// Gets or sets the namespace. public string Namespace { get; set; } - /// Gets or sets the classes code. - public string Classes { get; set; } + /// Gets or sets the types code. + public string TypesCode { get; set; } } } \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration.CSharp/NJsonSchema.CodeGeneration.CSharp.csproj b/src/NJsonSchema.CodeGeneration.CSharp/NJsonSchema.CodeGeneration.CSharp.csproj index 3f53ad4f9..124de1d5f 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/NJsonSchema.CodeGeneration.CSharp.csproj +++ b/src/NJsonSchema.CodeGeneration.CSharp/NJsonSchema.CodeGeneration.CSharp.csproj @@ -22,10 +22,12 @@ + - - + + + diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Templates/FileTemplate.cs b/src/NJsonSchema.CodeGeneration.CSharp/Templates/FileTemplate.cs index 4943cf15e..595e6d26e 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/Templates/FileTemplate.cs +++ b/src/NJsonSchema.CodeGeneration.CSharp/Templates/FileTemplate.cs @@ -44,7 +44,7 @@ public virtual string TransformText() this.Write("\r\n{\r\n #pragma warning disable // Disable all warnings\r\n\r\n "); #line 12 "C:\Data\Projects\NJsonSchema\src\NJsonSchema.CodeGeneration.CSharp\Templates\FileTemplate.tt" - this.Write(this.ToStringHelper.ToStringWithCulture(ConversionUtilities.Tab(Model.Classes, 1))); + this.Write(this.ToStringHelper.ToStringWithCulture(ConversionUtilities.Tab(Model.TypesCode, 1))); #line default #line hidden diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Templates/FileTemplate.tt b/src/NJsonSchema.CodeGeneration.CSharp/Templates/FileTemplate.tt index 4671ccf16..4b02a02ad 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/Templates/FileTemplate.tt +++ b/src/NJsonSchema.CodeGeneration.CSharp/Templates/FileTemplate.tt @@ -9,5 +9,5 @@ namespace <#=Model.Namespace#> { #pragma warning disable // Disable all warnings - <#=ConversionUtilities.Tab(Model.Classes, 1)#> + <#=ConversionUtilities.Tab(Model.TypesCode, 1)#> } \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.ToJson.liquid b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Liquid/Class.ToJson.liquid similarity index 100% rename from src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.ToJson.liquid rename to src/NJsonSchema.CodeGeneration.CSharp/Templates/Liquid/Class.ToJson.liquid diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.liquid b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Liquid/Class.liquid similarity index 100% rename from src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.liquid rename to src/NJsonSchema.CodeGeneration.CSharp/Templates/Liquid/Class.liquid diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Templates/Liquid/Enum.liquid b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Liquid/Enum.liquid new file mode 100644 index 000000000..17c3f6866 --- /dev/null +++ b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Liquid/Enum.liquid @@ -0,0 +1,14 @@ +{%- if HasDescription -%} +/// {{ Description | CSharpDocs }} +{%- endif -%} +[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "{{ ToolchainVersion }}")] +{{ TypeAccessModifier }} enum {{ Name }} +{ +{%- for enum in Enums -%} +{%- if IsStringEnum -%} + [System.Runtime.Serialization.EnumMember(Value = "{{ enum.Value }}")] +{%- endif -%} + {{ enum.Name }} = {{ enum.InternalValue }}, + +{%- endfor -%} +} \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration/CodeGeneratorSettingsBase.cs b/src/NJsonSchema.CodeGeneration/CodeGeneratorSettingsBase.cs index b6ea76f77..d7eaa34d0 100644 --- a/src/NJsonSchema.CodeGeneration/CodeGeneratorSettingsBase.cs +++ b/src/NJsonSchema.CodeGeneration/CodeGeneratorSettingsBase.cs @@ -18,6 +18,10 @@ public CodeGeneratorSettingsBase() { GenerateDefaultValues = true; ExcludedTypeNames = new string[] { }; + +#if DEBUG + UseLiquidTemplates = true; +#endif } /// Gets or sets the schema type (default: JsonSchema). @@ -46,7 +50,7 @@ public CodeGeneratorSettingsBase() public ITemplateFactory TemplateFactory { get; set; } /// Gets or sets a value indicating whether to use DotLiquid templates (experimental). - public bool UseLiquidTemplates { get; set; } = true; + public bool UseLiquidTemplates { get; set; } /// Gets or sets the template directory path. public string TemplateDirectory { get; set; } diff --git a/src/NJsonSchema.CodeGeneration/DefaultTemplateFactory.cs b/src/NJsonSchema.CodeGeneration/DefaultTemplateFactory.cs index db66c9ea6..10e8ee5e0 100644 --- a/src/NJsonSchema.CodeGeneration/DefaultTemplateFactory.cs +++ b/src/NJsonSchema.CodeGeneration/DefaultTemplateFactory.cs @@ -47,7 +47,7 @@ public virtual ITemplate CreateTemplate(string language, string template, object protected virtual string TryLoadEmbeddedLiquidTemplate(string language, string template) { var assembly = Assembly.Load(new AssemblyName("NJsonSchema.CodeGeneration." + language)); - var resourceName = "NJsonSchema.CodeGeneration." + language + ".Templates." + template + ".liquid"; + var resourceName = "NJsonSchema.CodeGeneration." + language + ".Templates.Liquid." + template + ".liquid"; var resource = assembly.GetManifestResourceStream(resourceName); if (resource != null) diff --git a/src/NJsonSchema.CodeGeneration/Models/ClassTemplateModelBase.cs b/src/NJsonSchema.CodeGeneration/Models/ClassTemplateModelBase.cs index a18d04750..6d3af3004 100644 --- a/src/NJsonSchema.CodeGeneration/Models/ClassTemplateModelBase.cs +++ b/src/NJsonSchema.CodeGeneration/Models/ClassTemplateModelBase.cs @@ -12,7 +12,7 @@ namespace NJsonSchema.CodeGeneration.Models { /// The class template base class. - public abstract class ClassTemplateModelBase + public abstract class ClassTemplateModelBase : TemplateModelBase { private readonly JsonSchema4 _schema; private readonly object _rootObject; diff --git a/src/NJsonSchema.CodeGeneration/Models/TemplateModelBase.cs b/src/NJsonSchema.CodeGeneration/Models/TemplateModelBase.cs new file mode 100644 index 000000000..6fce3deeb --- /dev/null +++ b/src/NJsonSchema.CodeGeneration/Models/TemplateModelBase.cs @@ -0,0 +1,17 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Rico Suter. All rights reserved. +// +// https://github.com/rsuter/NJsonSchema/blob/master/LICENSE.md +// Rico Suter, mail@rsuter.com +//----------------------------------------------------------------------- + +namespace NJsonSchema.CodeGeneration.Models +{ + /// The base template model. + public class TemplateModelBase + { + /// Gets the NJsonSchema toolchain version. + public string ToolchainVersion => JsonSchema4.ToolchainVersion; + } +} From 471a07e5d1dad982e1d19a28b1de0b9c71bd13b5 Mon Sep 17 00:00:00 2001 From: Rico Suter Date: Mon, 16 Oct 2017 00:21:36 +0200 Subject: [PATCH 11/19] Added more templates --- .../CSharpTypeResolver.cs | 7 +- .../NJsonSchema.CodeGeneration.CSharp.csproj | 10 ++ .../DateFormatConverterTemplate.Extensions.cs | 2 +- ...InheritanceConverterTemplate.Extensions.cs | 2 +- .../Templates/Liquid/Class.FromJson.liquid | 4 + .../Templates/Liquid/Class.Inpc.liquid | 8 ++ .../Templates/Liquid/Class.liquid | 16 +-- .../Liquid/DateFormatConverter.liquid | 10 ++ .../Templates/Liquid/File.liquid | 12 ++ .../Liquid/JsonInheritanceConverter.liquid | 120 ++++++++++++++++++ .../LiquidTemplate.cs | 5 + 11 files changed, 177 insertions(+), 19 deletions(-) create mode 100644 src/NJsonSchema.CodeGeneration.CSharp/Templates/Liquid/Class.FromJson.liquid create mode 100644 src/NJsonSchema.CodeGeneration.CSharp/Templates/Liquid/Class.Inpc.liquid create mode 100644 src/NJsonSchema.CodeGeneration.CSharp/Templates/Liquid/DateFormatConverter.liquid create mode 100644 src/NJsonSchema.CodeGeneration.CSharp/Templates/Liquid/File.liquid create mode 100644 src/NJsonSchema.CodeGeneration.CSharp/Templates/Liquid/JsonInheritanceConverter.liquid diff --git a/src/NJsonSchema.CodeGeneration.CSharp/CSharpTypeResolver.cs b/src/NJsonSchema.CodeGeneration.CSharp/CSharpTypeResolver.cs index e4cadba2a..e7dd917a2 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/CSharpTypeResolver.cs +++ b/src/NJsonSchema.CodeGeneration.CSharp/CSharpTypeResolver.cs @@ -9,7 +9,6 @@ using System.Collections.Generic; using System.Linq; using NJsonSchema.CodeGeneration.CSharp.Models; -using NJsonSchema.CodeGeneration.CSharp.Templates; namespace NJsonSchema.CodeGeneration.CSharp { @@ -93,7 +92,8 @@ public override CodeArtifactCollection GenerateTypes() Language = CodeArtifactLanguage.CSharp, TypeName = "JsonInheritanceConverter", - Code = new JsonInheritanceConverterTemplate(new JsonInheritanceConverterTemplateModel(Settings)).Render() + Code = Settings.TemplateFactory.CreateTemplate( + "CSharp", "JsonInheritanceConverter", new JsonInheritanceConverterTemplateModel(Settings)).Render() }); } @@ -105,7 +105,8 @@ public override CodeArtifactCollection GenerateTypes() Language = CodeArtifactLanguage.CSharp, TypeName = "DateFormatConverter", - Code = new DateFormatConverterTemplate(new DateFormatConverterTemplateModel(Settings)).Render() + Code = Settings.TemplateFactory.CreateTemplate( + "CSharp", "DateFormatConverter", new DateFormatConverterTemplateModel(Settings)).Render() }); } diff --git a/src/NJsonSchema.CodeGeneration.CSharp/NJsonSchema.CodeGeneration.CSharp.csproj b/src/NJsonSchema.CodeGeneration.CSharp/NJsonSchema.CodeGeneration.CSharp.csproj index 124de1d5f..a05507430 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/NJsonSchema.CodeGeneration.CSharp.csproj +++ b/src/NJsonSchema.CodeGeneration.CSharp/NJsonSchema.CodeGeneration.CSharp.csproj @@ -22,12 +22,22 @@ + + + + + + + + + + diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Templates/DateFormatConverterTemplate.Extensions.cs b/src/NJsonSchema.CodeGeneration.CSharp/Templates/DateFormatConverterTemplate.Extensions.cs index 3eec35bc4..57e995ee4 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/Templates/DateFormatConverterTemplate.Extensions.cs +++ b/src/NJsonSchema.CodeGeneration.CSharp/Templates/DateFormatConverterTemplate.Extensions.cs @@ -10,7 +10,7 @@ namespace NJsonSchema.CodeGeneration.CSharp.Templates { - internal partial class DateFormatConverterTemplate + internal partial class DateFormatConverterTemplate : ITemplate { public DateFormatConverterTemplate(DateFormatConverterTemplateModel model) { diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Templates/JsonInheritanceConverterTemplate.Extensions.cs b/src/NJsonSchema.CodeGeneration.CSharp/Templates/JsonInheritanceConverterTemplate.Extensions.cs index 2d5cdec59..7123d3fea 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/Templates/JsonInheritanceConverterTemplate.Extensions.cs +++ b/src/NJsonSchema.CodeGeneration.CSharp/Templates/JsonInheritanceConverterTemplate.Extensions.cs @@ -10,7 +10,7 @@ namespace NJsonSchema.CodeGeneration.CSharp.Templates { - internal partial class JsonInheritanceConverterTemplate + internal partial class JsonInheritanceConverterTemplate : ITemplate { public JsonInheritanceConverterTemplate(JsonInheritanceConverterTemplateModel model) { diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Templates/Liquid/Class.FromJson.liquid b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Liquid/Class.FromJson.liquid new file mode 100644 index 000000000..020640500 --- /dev/null +++ b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Liquid/Class.FromJson.liquid @@ -0,0 +1,4 @@ +public static {{ ClassName }} FromJson(string data) +{ + return Newtonsoft.Json.JsonConvert.DeserializeObject<{{ ClassName }}>(data{{ JsonSerializerParameterCode }}); +} \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Templates/Liquid/Class.Inpc.liquid b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Liquid/Class.Inpc.liquid new file mode 100644 index 000000000..67b1d44a3 --- /dev/null +++ b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Liquid/Class.Inpc.liquid @@ -0,0 +1,8 @@ +public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + +protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) +{ + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); +} \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Templates/Liquid/Class.liquid b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Liquid/Class.liquid index 589579931..06236a112 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/Templates/Liquid/Class.liquid +++ b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Liquid/Class.liquid @@ -66,24 +66,12 @@ set { _additionalProperties = value; } } -{%- endif -%} -{%- if RenderInpc -%} - public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; - {%- endif -%} {% template ToJson %} - public static {{ ClassName }} FromJson(string data) - { - return Newtonsoft.Json.JsonConvert.DeserializeObject<{{ ClassName }}>(data{{ JsonSerializerParameterCode }}); - } + {% template FromJson %} {%- if RenderInpc -%} - protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) - { - var handler = PropertyChanged; - if (handler != null) - handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); - } + {% template Inpc %} {%- endif -%} } \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Templates/Liquid/DateFormatConverter.liquid b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Liquid/DateFormatConverter.liquid new file mode 100644 index 000000000..612652bfc --- /dev/null +++ b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Liquid/DateFormatConverter.liquid @@ -0,0 +1,10 @@ +{%- if GenerateDateFormatConverterClass -%} +[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "{{ ToolchainVersion }}")] +internal class DateFormatConverter : Newtonsoft.Json.Converters.IsoDateTimeConverter +{ + public DateFormatConverter() + { + DateTimeFormat = "yyyy-MM-dd"; + } +} +{%- endif -%} \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Templates/Liquid/File.liquid b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Liquid/File.liquid new file mode 100644 index 000000000..770bf847a --- /dev/null +++ b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Liquid/File.liquid @@ -0,0 +1,12 @@ +//---------------------- +// +// Generated using the NJsonSchema v{{ ToolchainVersion }} (http://NJsonSchema.org) +// +//---------------------- + +namespace {{ Namespace }} +{ + #pragma warning disable // Disable all warnings + + {{ TypesCode | tab: 1 }} +} \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Templates/Liquid/JsonInheritanceConverter.liquid b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Liquid/JsonInheritanceConverter.liquid new file mode 100644 index 000000000..80223b4f9 --- /dev/null +++ b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Liquid/JsonInheritanceConverter.liquid @@ -0,0 +1,120 @@ +{%- if GenerateJsonInheritanceAttributeClass -%} +[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "{{ ToolchainVersion }}")] +[System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = true)] +internal class JsonInheritanceAttribute : System.Attribute +{ + public JsonInheritanceAttribute(string key, System.Type type) + { + Key = key; + Type = type; + } + + public string Key { get; } + + public System.Type Type { get; } +} + +{%- endif -%} +{%- if GenerateJsonInheritanceConverterClass -%} +[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "{{ ToolchainVersion }}")] +internal class JsonInheritanceConverter : Newtonsoft.Json.JsonConverter +{ + internal static readonly string DefaultDiscriminatorName = "discriminator"; + + private readonly string _discriminator; + + [System.ThreadStatic] + private static bool _isReading; + + [System.ThreadStatic] + private static bool _isWriting; + + public JsonInheritanceConverter() + { + _discriminator = DefaultDiscriminatorName; + } + + public JsonInheritanceConverter(string discriminator) + { + _discriminator = discriminator; + } + + public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) + { + try + { + _isWriting = true; + + var jObject = Newtonsoft.Json.Linq.JObject.FromObject(value, serializer); + jObject.AddFirst(new Newtonsoft.Json.Linq.JProperty(_discriminator, value.GetType().Name)); + writer.WriteToken(jObject.CreateReader()); + } + finally + { + _isWriting = false; + } + } + + public override bool CanWrite + { + get + { + if (_isWriting) + { + _isWriting = false; + return false; + } + return true; + } + } + + public override bool CanRead + { + get + { + if (_isReading) + { + _isReading = false; + return false; + } + return true; + } + } + + public override bool CanConvert(System.Type objectType) + { + return true; + } + + public override object ReadJson(Newtonsoft.Json.JsonReader reader, System.Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer) + { + var jObject = serializer.Deserialize(reader); + if (jObject == null) + return null; + + var discriminator = Newtonsoft.Json.Linq.Extensions.Value(jObject.GetValue(_discriminator)); + var subtype = GetObjectSubtype(objectType, discriminator); + + try + { + _isReading = true; + return serializer.Deserialize(jObject.CreateReader(), subtype); + } + finally + { + _isReading = false; + } + } + + private System.Type GetObjectSubtype(System.Type objectType, string discriminator) + { + foreach (var type in System.Reflection.CustomAttributeExtensions.GetCustomAttributes(System.Reflection.IntrospectionExtensions.GetTypeInfo(objectType), false)) + { + if (type.Key == discriminator) + return type.Type; + } + + return objectType; + } +} +{%- endif -%} \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration/LiquidTemplate.cs b/src/NJsonSchema.CodeGeneration/LiquidTemplate.cs index e46b4473e..a534d6b84 100644 --- a/src/NJsonSchema.CodeGeneration/LiquidTemplate.cs +++ b/src/NJsonSchema.CodeGeneration/LiquidTemplate.cs @@ -59,6 +59,11 @@ public static string CSharpDocs(string input) { return ConversionUtilities.ConvertCSharpDocBreaks(input, 0); } + + public static string Tab(Context context, string input, int tabCount) + { + return ConversionUtilities.Tab(input, tabCount); + } } internal class TemplateTag : Tag From c8d3216e4741cfd89d2f9182eece9f2c46e3533d Mon Sep 17 00:00:00 2001 From: Rico Suter Date: Mon, 16 Oct 2017 00:55:15 +0200 Subject: [PATCH 12/19] Added extension template imports --- .../Templates/Liquid/Class.liquid | 1 + .../LiquidTemplate.cs | 33 +++++++++++-------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Templates/Liquid/Class.liquid b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Liquid/Class.liquid index 06236a112..816152c32 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/Templates/Liquid/Class.liquid +++ b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Liquid/Class.liquid @@ -8,6 +8,7 @@ {%- endfor -%} {%- endif -%} [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "{{ToolchainVersion}}")] +{% template Annotations -%} {{ TypeAccessModifier }} partial class {{ClassName}} {{InheritanceCode}} { {%- if RenderInpc -%} diff --git a/src/NJsonSchema.CodeGeneration/LiquidTemplate.cs b/src/NJsonSchema.CodeGeneration/LiquidTemplate.cs index a534d6b84..93264530d 100644 --- a/src/NJsonSchema.CodeGeneration/LiquidTemplate.cs +++ b/src/NJsonSchema.CodeGeneration/LiquidTemplate.cs @@ -6,6 +6,7 @@ // Rico Suter, mail@rsuter.com //----------------------------------------------------------------------- +using System; using System.Collections.Generic; using System.IO; using System.Text.RegularExpressions; @@ -34,8 +35,8 @@ public string Render() { Template.RegisterTag("template"); - var data = Regex.Replace(_data, "(\n(( )*?)\\{% template .*?) %}", m => - m.Groups[1].Value + " " + m.Groups[2].Value.Length / 4 + " %}", + var data = Regex.Replace(_data, "(\n(( )*?)\\{% template .*?) %}", m => + m.Groups[1].Value + " " + m.Groups[2].Value.Length / 4 + " %}", RegexOptions.Singleline); var template = Template.Parse(data); @@ -85,17 +86,23 @@ public override void Initialize(string tagName, string markup, List toke public override void Render(Context context, TextWriter result) { - var hash = new Hash(); - foreach (var environment in context.Environments) - hash.Merge(environment); - - var settings = (CodeGeneratorSettingsBase)hash[SettingsKey]; - var template = settings.TemplateFactory.CreateTemplate( - (string)hash[LanguageKey], - !string.IsNullOrEmpty(_template) ? (string)hash[TemplateKey] + "." + _template : (string)hash[TemplateKey] + "!", - hash); - - result.Write(ConversionUtilities.Tab(template.Render(), _tab)); + try + { + var hash = new Hash(); + foreach (var environment in context.Environments) + hash.Merge(environment); + + var settings = (CodeGeneratorSettingsBase)hash[SettingsKey]; + var template = settings.TemplateFactory.CreateTemplate( + (string)hash[LanguageKey], + !string.IsNullOrEmpty(_template) ? (string)hash[TemplateKey] + "." + _template : (string)hash[TemplateKey] + "!", + hash); + + result.Write(ConversionUtilities.Tab(template.Render(), _tab)); + } + catch (InvalidOperationException) + { + } } } } \ No newline at end of file From e66eb5bafb8320a127ba5d301c7db1663b2b1173 Mon Sep 17 00:00:00 2001 From: Rico Suter Date: Mon, 16 Oct 2017 10:38:12 +0200 Subject: [PATCH 13/19] Added JsonSchemaVisitor --- src/NJsonSchema/JsonSchemaVisitor.cs | 135 +++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 src/NJsonSchema/JsonSchemaVisitor.cs diff --git a/src/NJsonSchema/JsonSchemaVisitor.cs b/src/NJsonSchema/JsonSchemaVisitor.cs new file mode 100644 index 000000000..42fc04f74 --- /dev/null +++ b/src/NJsonSchema/JsonSchemaVisitor.cs @@ -0,0 +1,135 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Rico Suter. All rights reserved. +// +// https://github.com/rsuter/NJsonSchema/blob/master/LICENSE.md +// Rico Suter, mail@rsuter.com +//----------------------------------------------------------------------- + +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using NJsonSchema.Infrastructure; +using NJsonSchema.References; + +namespace NJsonSchema +{ + /// Visitor to transform an object with objects. + public abstract class JsonSchemaVisitor + { + /// Processes an object. + /// The object to process. + /// The task. + public async Task VisitAsync(object obj) + { + await VisitAsync(obj, "#", null, new HashSet()); + } + + /// Processes an object. + /// The object to process. + /// The path + /// The type name hint. + /// The checked objects. + /// The task. + protected virtual async Task VisitAsync(object obj, string path, string typeNameHint, ISet checkedObjects) + { + if (checkedObjects.Contains(obj)) + return; + checkedObjects.Add(obj); + + if (obj is IJsonReference reference) + { + await VisitJsonReferenceAsync(reference, path); + } + if (obj is JsonSchema4 schema) + { + await VisitSchemaAsync(schema, path, typeNameHint); + + if (schema.AdditionalItemsSchema != null) + await VisitAsync(schema.AdditionalItemsSchema, path + "/additionalProperties", null, checkedObjects); + + if (schema.AdditionalPropertiesSchema != null) + await VisitAsync(schema.AdditionalItemsSchema, path + "/additionalProperties", null, checkedObjects); + + if (schema.Item != null) + await VisitAsync(schema.Item, path + "/items", null, checkedObjects); + + for (var i = 0; i < schema.Items.Count; i++) + await VisitAsync(schema.Items.ElementAt(i), path + "/items[" + i + "]", null, checkedObjects); + + for (var i = 0; i < schema.AllOf.Count; i++) + await VisitAsync(schema.AllOf.ElementAt(i), path + "/allOf[" + i + "]", null, checkedObjects); + + for (var i = 0; i < schema.AnyOf.Count; i++) + await VisitAsync(schema.AnyOf.ElementAt(i), path + "/anyOf[" + i + "]", null, checkedObjects); + + for (var i = 0; i < schema.OneOf.Count; i++) + await VisitAsync(schema.OneOf.ElementAt(i), path + "/oneOf[" + i + "]", null, checkedObjects); + + if (schema.Not != null) + await VisitAsync(schema.Not, path + "/not", null, checkedObjects); + + foreach (var p in schema.Properties) + await VisitAsync(p.Value, path + "/properties/" + p.Key, p.Key, checkedObjects); + + foreach (var p in schema.PatternProperties) + await VisitAsync(p.Value, path + "/patternProperties/" + p.Key, null, checkedObjects); + + foreach (var p in schema.Definitions) + await VisitAsync(p.Value, path + "/definitions/" + p.Key, p.Key, checkedObjects); + } + else if (obj != null && !(obj is string) && !checkedObjects.Contains(obj)) + { + // Reflection fallback + + if (obj is IDictionary dictionary) + { + foreach (var key in dictionary.Keys) + await VisitAsync(dictionary[key], path + "/" + key, key.ToString(), checkedObjects); + } + else if (obj is IEnumerable enumerable) + { + var i = 0; + foreach (var item in enumerable) + { + await VisitAsync(item, path + "[" + i + "]", null, checkedObjects); + i++; + } + } + else + { + foreach (var member in ReflectionCache.GetPropertiesAndFields(obj.GetType())) + { + var value = member.GetValue(obj); + if (value != null) + await VisitAsync(value, path + "/" + member.GetName(), member.GetName(), checkedObjects); + } + } + } + } + + /// Called when a is visited. + /// The visited schema. + /// The path. + /// The type name hint. + /// The task. +#pragma warning disable 1998 + protected virtual async Task VisitSchemaAsync(JsonSchema4 schema, string path, string typeNameHint) +#pragma warning restore 1998 + { + // must be empty + } + + /// Called when a is visited. + /// The visited schema. + /// The path. + /// The task. +#pragma warning disable 1998 + protected virtual async Task VisitJsonReferenceAsync(IJsonReference reference, string path) +#pragma warning restore 1998 + { + // must be empty + } + } +} From f9f7b57426ffc3d0861bd8afd4757b29cbec5875 Mon Sep 17 00:00:00 2001 From: Rico Suter Date: Mon, 16 Oct 2017 10:39:34 +0200 Subject: [PATCH 14/19] Fixed JsonSchemaVisitor --- src/NJsonSchema/JsonSchemaVisitor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NJsonSchema/JsonSchemaVisitor.cs b/src/NJsonSchema/JsonSchemaVisitor.cs index 42fc04f74..03313303d 100644 --- a/src/NJsonSchema/JsonSchemaVisitor.cs +++ b/src/NJsonSchema/JsonSchemaVisitor.cs @@ -47,10 +47,10 @@ protected virtual async Task VisitAsync(object obj, string path, string typeName await VisitSchemaAsync(schema, path, typeNameHint); if (schema.AdditionalItemsSchema != null) - await VisitAsync(schema.AdditionalItemsSchema, path + "/additionalProperties", null, checkedObjects); + await VisitAsync(schema.AdditionalItemsSchema, path + "/additionalItems", null, checkedObjects); if (schema.AdditionalPropertiesSchema != null) - await VisitAsync(schema.AdditionalItemsSchema, path + "/additionalProperties", null, checkedObjects); + await VisitAsync(schema.AdditionalPropertiesSchema, path + "/additionalProperties", null, checkedObjects); if (schema.Item != null) await VisitAsync(schema.Item, path + "/items", null, checkedObjects); From 57ea92a86d727780292307fb968d99c3ccfd9a8f Mon Sep 17 00:00:00 2001 From: Rico Suter Date: Mon, 16 Oct 2017 14:53:27 +0200 Subject: [PATCH 15/19] Make ApplyDataAnnotations virtual, closes #512 --- src/NJsonSchema/Generation/JsonSchemaGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NJsonSchema/Generation/JsonSchemaGenerator.cs b/src/NJsonSchema/Generation/JsonSchemaGenerator.cs index 0c48eb935..602864c7d 100644 --- a/src/NJsonSchema/Generation/JsonSchemaGenerator.cs +++ b/src/NJsonSchema/Generation/JsonSchemaGenerator.cs @@ -797,7 +797,7 @@ private static bool HasDataContractAttribute(Type parentType) /// The schema. /// The property type description. /// The attributes. - public void ApplyDataAnnotations(JsonSchema4 schema, JsonTypeDescription typeDescription, IEnumerable parentAttributes) + public virtual void ApplyDataAnnotations(JsonSchema4 schema, JsonTypeDescription typeDescription, IEnumerable parentAttributes) { // TODO: Refactor out From 5be00e5a3aba7d41957e91309b2ff39e5f567e8a Mon Sep 17 00:00:00 2001 From: Rico Suter Date: Mon, 16 Oct 2017 17:32:26 +0200 Subject: [PATCH 16/19] Added async schema factory to ObjectTypeMapper --- .../Generation/TypeMappers/ObjectTypeMapper.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/NJsonSchema/Generation/TypeMappers/ObjectTypeMapper.cs b/src/NJsonSchema/Generation/TypeMappers/ObjectTypeMapper.cs index 44b46ee84..2dd232ac2 100644 --- a/src/NJsonSchema/Generation/TypeMappers/ObjectTypeMapper.cs +++ b/src/NJsonSchema/Generation/TypeMappers/ObjectTypeMapper.cs @@ -14,7 +14,7 @@ namespace NJsonSchema.Generation.TypeMappers /// Maps .NET type to a generated JSON Schema describing an object. public class ObjectTypeMapper : ITypeMapper { - private readonly Func _schemaFactory; + private readonly Func> _schemaFactory; /// Initializes a new instance of the class. /// Type of the mapped. @@ -24,10 +24,21 @@ public ObjectTypeMapper(Type mappedType, JsonSchema4 schema) { } + /// Initializes a new instance of the class. /// Type of the mapped. /// The schema factory. public ObjectTypeMapper(Type mappedType, Func schemaFactory) +#pragma warning disable 1998 + : this(mappedType, async (schemaGenerator, schemaResolver) => schemaFactory(schemaGenerator, schemaResolver)) +#pragma warning restore 1998 + { + } + + /// Initializes a new instance of the class. + /// Type of the mapped. + /// The schema factory. + public ObjectTypeMapper(Type mappedType, Func> schemaFactory) { _schemaFactory = schemaFactory; MappedType = mappedType; @@ -47,7 +58,7 @@ public async Task GenerateSchemaAsync(JsonSchema4 schema, TypeMapperContext cont #pragma warning restore 1998 { if (!context.JsonSchemaResolver.HasSchema(MappedType, false)) - context.JsonSchemaResolver.AddSchema(MappedType, false, _schemaFactory(context.JsonSchemaGenerator, context.JsonSchemaResolver)); + context.JsonSchemaResolver.AddSchema(MappedType, false, await _schemaFactory(context.JsonSchemaGenerator, context.JsonSchemaResolver)); schema.Reference = context.JsonSchemaResolver.GetSchema(MappedType, false); } From 4d8139fdfbaac5555e87765d1bc40607a50b5141 Mon Sep 17 00:00:00 2001 From: Rico Suter Date: Mon, 16 Oct 2017 19:48:56 +0200 Subject: [PATCH 17/19] Improved schema reference handling --- .../NJsonSchema.Tests.csproj | 13 ++ .../References/LocalReferencesTests.cs | 82 +++++++++ .../LocalReferencesTests/animal.json | 10 ++ .../LocalReferencesTests/collection.json | 8 + .../schema_with_collection_reference.json | 16 ++ .../schema_with_reference.json | 8 + ...PathUtilitiesGetObjectFromJsonPathTests.cs | 10 +- .../Infrastructure/ReflectionCache.cs | 15 +- src/NJsonSchema/JsonReferenceResolver.cs | 106 ++++++----- src/NJsonSchema/JsonSchema4.Reference.cs | 4 +- src/NJsonSchema/JsonSchema4.cs | 17 +- .../JsonSchemaReferenceUtilities.cs | 133 +++++++------- src/NJsonSchema/JsonSchemaVisitor.cs | 168 +++++++++++++----- .../References/JsonReferenceBase.cs | 4 +- .../References/JsonReferenceExtensions.cs | 9 +- 15 files changed, 436 insertions(+), 167 deletions(-) create mode 100644 src/NJsonSchema.Tests/References/LocalReferencesTests.cs create mode 100644 src/NJsonSchema.Tests/References/LocalReferencesTests/animal.json create mode 100644 src/NJsonSchema.Tests/References/LocalReferencesTests/collection.json create mode 100644 src/NJsonSchema.Tests/References/LocalReferencesTests/schema_with_collection_reference.json create mode 100644 src/NJsonSchema.Tests/References/LocalReferencesTests/schema_with_reference.json diff --git a/src/NJsonSchema.Tests/NJsonSchema.Tests.csproj b/src/NJsonSchema.Tests/NJsonSchema.Tests.csproj index fdcfd0aeb..900e99a35 100644 --- a/src/NJsonSchema.Tests/NJsonSchema.Tests.csproj +++ b/src/NJsonSchema.Tests/NJsonSchema.Tests.csproj @@ -92,6 +92,7 @@ + @@ -136,6 +137,18 @@ + + Always + + + Always + + + Always + + + Always + diff --git a/src/NJsonSchema.Tests/References/LocalReferencesTests.cs b/src/NJsonSchema.Tests/References/LocalReferencesTests.cs new file mode 100644 index 000000000..02b204abe --- /dev/null +++ b/src/NJsonSchema.Tests/References/LocalReferencesTests.cs @@ -0,0 +1,82 @@ +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace NJsonSchema.Tests.References +{ + [TestClass] + public class LocalReferencesTests + { + [TestMethod] + public async Task When_definitions_is_nested_then_refs_work() + { + //// Arrange + var json = @"{ + ""type"": ""object"", + ""properties"": { + ""foo"": { + ""$ref"": ""#/definitions/collection/bar"" + } + }, + ""definitions"": { + ""collection"": { + ""bar"": { + ""type"": ""integer"" + } + } + } +}"; + + //// Act + var schema = await JsonSchema4.FromJsonAsync(json); + var j = schema.ToJson(); + + //// Assert + Assert.AreEqual(JsonObjectType.Integer, schema.Properties["foo"].ActualPropertySchema.Type); + } + + [TestMethod] + public async Task When_schema_references_collection_in_definitions_it_works() + { + //// Arrange + var path = "References/LocalReferencesTests/schema_with_collection_reference.json"; + + //// Act + var schema = await JsonSchema4.FromFileAsync(path); + var json = schema.ToJson(); + + //// Assert + Assert.AreEqual(JsonObjectType.Integer, schema.Properties["foo"].ActualPropertySchema.Type); + Assert.AreEqual(1, schema.Definitions.Count); + Assert.AreEqual("./collection.json", schema.Definitions["collection"].DocumentPath); + } + + [TestMethod] + public async Task When_schema_references_external_schema_then_it_is_inlined_with_ToJson() + { + //// Arrange + var path = "References/LocalReferencesTests/schema_with_reference.json"; + + //// Act + var schema = await JsonSchema4.FromFileAsync(path); + var json = schema.ToJson(); + + //// Assert + Assert.IsTrue(schema.Definitions.ContainsKey("Animal")); + Assert.IsTrue(json.Contains("\"$ref\": \"#/definitions/Animal\"")); + } + + [TestMethod] + public async Task When_schema_references_external_schema_then_it_is_removed_with_ToJsonWithExternalReferences() + { + //// Arrange + var path = "References/LocalReferencesTests/schema_with_reference.json"; + + //// Act + var schema = await JsonSchema4.FromFileAsync(path); + var json = schema.ToJsonWithExternalReferences(); + + //// Assert + Assert.AreEqual(0, schema.Definitions.Count); + } + } +} diff --git a/src/NJsonSchema.Tests/References/LocalReferencesTests/animal.json b/src/NJsonSchema.Tests/References/LocalReferencesTests/animal.json new file mode 100644 index 000000000..f8b2defc0 --- /dev/null +++ b/src/NJsonSchema.Tests/References/LocalReferencesTests/animal.json @@ -0,0 +1,10 @@ +{ + "type": "object", + "properties": { + "bar": { + "name": { + "type": "string" + } + } + } +} \ No newline at end of file diff --git a/src/NJsonSchema.Tests/References/LocalReferencesTests/collection.json b/src/NJsonSchema.Tests/References/LocalReferencesTests/collection.json new file mode 100644 index 000000000..786047629 --- /dev/null +++ b/src/NJsonSchema.Tests/References/LocalReferencesTests/collection.json @@ -0,0 +1,8 @@ +{ + "myInt": { + "type": "integer" + }, + "myBool": { + "type": "boolean" + } +} \ No newline at end of file diff --git a/src/NJsonSchema.Tests/References/LocalReferencesTests/schema_with_collection_reference.json b/src/NJsonSchema.Tests/References/LocalReferencesTests/schema_with_collection_reference.json new file mode 100644 index 000000000..3bd21f0e7 --- /dev/null +++ b/src/NJsonSchema.Tests/References/LocalReferencesTests/schema_with_collection_reference.json @@ -0,0 +1,16 @@ +{ + "type": "object", + "properties": { + "bar": { + "$ref": "./collection.json#/myBool" + }, + "foo": { + "$ref": "#/definitions/collection/myInt" + } + }, + "definitions": { + "collection": { + "$ref": "./collection.json" + } + } +} \ No newline at end of file diff --git a/src/NJsonSchema.Tests/References/LocalReferencesTests/schema_with_reference.json b/src/NJsonSchema.Tests/References/LocalReferencesTests/schema_with_reference.json new file mode 100644 index 000000000..8838f8466 --- /dev/null +++ b/src/NJsonSchema.Tests/References/LocalReferencesTests/schema_with_reference.json @@ -0,0 +1,8 @@ +{ + "type": "object", + "properties": { + "foo": { + "$ref": "./animal.json" + } + } +} \ No newline at end of file diff --git a/src/NJsonSchema.Tests/Schema/JsonPathUtilitiesGetObjectFromJsonPathTests.cs b/src/NJsonSchema.Tests/Schema/JsonPathUtilitiesGetObjectFromJsonPathTests.cs index 1356ff756..3ecc49a4c 100644 --- a/src/NJsonSchema.Tests/Schema/JsonPathUtilitiesGetObjectFromJsonPathTests.cs +++ b/src/NJsonSchema.Tests/Schema/JsonPathUtilitiesGetObjectFromJsonPathTests.cs @@ -99,18 +99,20 @@ public async Task When_object_is_root_then_path_should_be_built_correctly() public async Task When_object_is_in_external_file_then_path_should_be_built_correctly() { //// Arrange - var schemaToReference = new JsonSchema4 {DocumentPath = "some_schema.json"}; var referencingSchema = new JsonSchema4 { DocumentPath = "other_schema.json", - Reference = schemaToReference + Reference = new JsonSchema4 + { + DocumentPath = "some_schema.json" + } }; //// Act - var result = referencingSchema.ToJson(); + var result = referencingSchema.ToJsonWithExternalReferences(); //// Assert - Assert.IsTrue(result.Contains("some_schema.json#")); + Assert.IsTrue(result.Contains("some_schema.json")); } } } \ No newline at end of file diff --git a/src/NJsonSchema/Infrastructure/ReflectionCache.cs b/src/NJsonSchema/Infrastructure/ReflectionCache.cs index c6ff0c3e8..dd8c235b7 100644 --- a/src/NJsonSchema/Infrastructure/ReflectionCache.cs +++ b/src/NJsonSchema/Infrastructure/ReflectionCache.cs @@ -117,7 +117,20 @@ public object GetValue(object obj) { if (MemberInfo is PropertyInfo) return ((PropertyInfo)MemberInfo).GetValue(obj); - return ((FieldInfo)MemberInfo).GetValue(obj); + else + return ((FieldInfo)MemberInfo).GetValue(obj); + } + + /// Gets the value of the property or field. + /// The object. + /// The value. + /// The value. + public void SetValue(object obj, object value) + { + if (MemberInfo is PropertyInfo) + ((PropertyInfo)MemberInfo).SetValue(obj, value); + else + ((FieldInfo)MemberInfo).SetValue(obj, value); } /// Gets the name of the property for JSON serialization. diff --git a/src/NJsonSchema/JsonReferenceResolver.cs b/src/NJsonSchema/JsonReferenceResolver.cs index 6eafc5080..169758cb0 100644 --- a/src/NJsonSchema/JsonReferenceResolver.cs +++ b/src/NJsonSchema/JsonReferenceResolver.cs @@ -46,40 +46,18 @@ public void AddDocumentReference(string documentPath, IJsonReference schema) /// Could not resolve the JSON path. public async Task ResolveReferenceAsync(object rootObject, string jsonPath) { - if (jsonPath == "#") - { - if (rootObject is IJsonReference) - return (IJsonReference)rootObject; - - throw new InvalidOperationException("Could not resolve the JSON path '#' because the root object is not a JsonSchema4."); - } - else if (jsonPath.StartsWith("#/")) - { - return ResolveDocumentReference(rootObject, jsonPath); - } - else if (jsonPath.StartsWith("http://") || jsonPath.StartsWith("https://")) - return await ResolveUrlReferenceWithAlreadyResolvedCheckAsync(jsonPath, jsonPath).ConfigureAwait(false); - else - { - var documentPathProvider = rootObject as IDocumentPathProvider; + return await ResolveReferenceAsync(rootObject, jsonPath, true); + } - var documentPath = documentPathProvider?.DocumentPath; - if (documentPath != null) - { - if (documentPath.StartsWith("http://") || documentPath.StartsWith("https://")) - { - var url = new Uri(new Uri(documentPath), jsonPath).ToString(); - return await ResolveUrlReferenceWithAlreadyResolvedCheckAsync(url, jsonPath).ConfigureAwait(false); - } - else - { - var filePath = DynamicApis.PathCombine(DynamicApis.PathGetDirectoryName(documentPath), jsonPath); - return await ResolveFileReferenceWithAlreadyResolvedCheckAsync(filePath, jsonPath).ConfigureAwait(false); - } - } - else - throw new NotSupportedException("Could not resolve the JSON path '" + jsonPath + "' because no document path is available."); - } + /// Gets the object from the given JSON path. + /// The root object. + /// The JSON path. + /// The JSON Schema or null when the object could not be found. + /// Could not resolve the JSON path. + /// Could not resolve the JSON path. + public async Task ResolveReferenceWithoutAppendAsync(object rootObject, string jsonPath) + { + return await ResolveReferenceAsync(rootObject, jsonPath, false); } /// Resolves a document reference. @@ -113,21 +91,61 @@ public virtual async Task ResolveUrlReferenceAsync(string url) return await JsonSchema4.FromUrlAsync(url, schema => this).ConfigureAwait(false); } - private async Task ResolveFileReferenceWithAlreadyResolvedCheckAsync(string fullJsonPath, string jsonPath) + private async Task ResolveReferenceAsync(object rootObject, string jsonPath, bool append) + { + if (jsonPath == "#") + { + if (rootObject is IJsonReference) + return (IJsonReference)rootObject; + + throw new InvalidOperationException("Could not resolve the JSON path '#' because the root object is not a JsonSchema4."); + } + else if (jsonPath.StartsWith("#/")) + { + return ResolveDocumentReference(rootObject, jsonPath); + } + else if (jsonPath.StartsWith("http://") || jsonPath.StartsWith("https://")) + return await ResolveUrlReferenceWithAlreadyResolvedCheckAsync(jsonPath, jsonPath, append).ConfigureAwait(false); + else + { + var documentPathProvider = rootObject as IDocumentPathProvider; + + var documentPath = documentPathProvider?.DocumentPath; + if (documentPath != null) + { + if (documentPath.StartsWith("http://") || documentPath.StartsWith("https://")) + { + var url = new Uri(new Uri(documentPath), jsonPath).ToString(); + return await ResolveUrlReferenceWithAlreadyResolvedCheckAsync(url, jsonPath, append).ConfigureAwait(false); + } + else + { + var filePath = DynamicApis.PathCombine(DynamicApis.PathGetDirectoryName(documentPath), jsonPath); + return await ResolveFileReferenceWithAlreadyResolvedCheckAsync(filePath, jsonPath, append).ConfigureAwait(false); + } + } + else + throw new NotSupportedException("Could not resolve the JSON path '" + jsonPath + "' because no document path is available."); + } + } + + private async Task ResolveFileReferenceWithAlreadyResolvedCheckAsync(string fullJsonPath, string jsonPath, bool append) { try { var arr = Regex.Split(fullJsonPath, @"(?=#)"); - if (!_resolvedObjects.ContainsKey(arr[0])) + var filePath = arr[0]; + if (!_resolvedObjects.ContainsKey(filePath)) { - var schema = await ResolveFileReferenceAsync(arr[0]).ConfigureAwait(false); - if (schema is JsonSchema4) - _schemaResolver.AppendSchema((JsonSchema4)schema, null); + var schema = await ResolveFileReferenceAsync(filePath).ConfigureAwait(false); + schema.DocumentPath = jsonPath; + if (schema is JsonSchema4 && append) + _schemaResolver.AppendSchema((JsonSchema4)schema, filePath.Split('/', '\\').Last().Split('.').First()); - _resolvedObjects[arr[0]] = schema; + _resolvedObjects[filePath] = schema; } - var result = _resolvedObjects[arr[0]]; + var result = _resolvedObjects[filePath]; return arr.Length == 1 ? result : await ResolveReferenceAsync(result, arr[1]).ConfigureAwait(false); } catch (Exception exception) @@ -136,7 +154,7 @@ private async Task ResolveFileReferenceWithAlreadyResolvedCheckA } } - private async Task ResolveUrlReferenceWithAlreadyResolvedCheckAsync(string fullJsonPath, string jsonPath) + private async Task ResolveUrlReferenceWithAlreadyResolvedCheckAsync(string fullJsonPath, string jsonPath, bool append) { try { @@ -144,7 +162,8 @@ private async Task ResolveUrlReferenceWithAlreadyResolvedCheckAs if (!_resolvedObjects.ContainsKey(arr[0])) { var schema = await ResolveUrlReferenceAsync(arr[0]).ConfigureAwait(false); - if (schema is JsonSchema4) + schema.DocumentPath = jsonPath; + if (schema is JsonSchema4 && append) _schemaResolver.AppendSchema((JsonSchema4)schema, null); _resolvedObjects[arr[0]] = schema; @@ -164,6 +183,9 @@ private IJsonReference ResolveDocumentReference(object obj, List segment if (obj == null || obj is string || checkedObjects.Contains(obj)) return null; + if (obj is IJsonReference reference && reference.Reference != null) + obj = reference.Reference; + if (segments.Count == 0) return obj as IJsonReference; diff --git a/src/NJsonSchema/JsonSchema4.Reference.cs b/src/NJsonSchema/JsonSchema4.Reference.cs index 1f9f97d88..79d831809 100644 --- a/src/NJsonSchema/JsonSchema4.Reference.cs +++ b/src/NJsonSchema/JsonSchema4.Reference.cs @@ -60,8 +60,8 @@ private JsonSchema4 GetActualSchema(IList checkedSchemas) if (checkedSchemas.Contains(this)) throw new InvalidOperationException("Cyclic references detected."); - if (ReferencePath != null && Reference == null) - throw new InvalidOperationException("The schema reference path '" + ReferencePath + "' has not been resolved."); + if (((IJsonReferenceBase)this).ReferencePath != null && Reference == null) + throw new InvalidOperationException("The schema reference path '" + ((IJsonReferenceBase)this).ReferencePath + "' has not been resolved."); if (HasReference) { diff --git a/src/NJsonSchema/JsonSchema4.cs b/src/NJsonSchema/JsonSchema4.cs index 601c81940..98d4a488d 100644 --- a/src/NJsonSchema/JsonSchema4.cs +++ b/src/NJsonSchema/JsonSchema4.cs @@ -698,18 +698,23 @@ public virtual bool IsNullable(SchemaType schemaType) /// The JSON string. public string ToJson() { - var settings = new JsonSchemaGeneratorSettings(); - return ToJson(settings); + var oldSchema = SchemaVersion; + SchemaVersion = "http://json-schema.org/draft-04/schema#"; + JsonSchemaReferenceUtilities.UpdateSchemaReferencePaths(this, false); + var json = JsonSchemaReferenceUtilities.ConvertPropertyReferences(JsonConvert.SerializeObject(this, Formatting.Indented)); + SchemaVersion = oldSchema; + return json; } - /// Serializes the to a JSON string. - /// The settings. + /// Serializes the to a JSON string and removes externally loaded schemas. /// The JSON string. - public string ToJson(JsonSchemaGeneratorSettings settings) + [Obsolete("Not ready yet as it has side-effects on the schema.")] + public string ToJsonWithExternalReferences() { + // TODO: Copy "this" schema first (high-prio) var oldSchema = SchemaVersion; SchemaVersion = "http://json-schema.org/draft-04/schema#"; - JsonSchemaReferenceUtilities.UpdateSchemaReferencePaths(this); + JsonSchemaReferenceUtilities.UpdateSchemaReferencePaths(this, true); var json = JsonSchemaReferenceUtilities.ConvertPropertyReferences(JsonConvert.SerializeObject(this, Formatting.Indented)); SchemaVersion = oldSchema; return json; diff --git a/src/NJsonSchema/JsonSchemaReferenceUtilities.cs b/src/NJsonSchema/JsonSchemaReferenceUtilities.cs index 2ce56a9e3..76450e41c 100644 --- a/src/NJsonSchema/JsonSchemaReferenceUtilities.cs +++ b/src/NJsonSchema/JsonSchemaReferenceUtilities.cs @@ -6,13 +6,9 @@ // Rico Suter, mail@rsuter.com //----------------------------------------------------------------------- -using System.Collections; using System.Collections.Generic; using System.Linq; -using System.Reflection; using System.Threading.Tasks; -using Newtonsoft.Json.Linq; -using NJsonSchema.Infrastructure; using NJsonSchema.References; namespace NJsonSchema @@ -26,7 +22,8 @@ public static class JsonSchemaReferenceUtilities /// The root object. public static async Task UpdateSchemaReferencesAsync(object rootObject, JsonReferenceResolver referenceResolver) { - await UpdateSchemaReferencesAsync(rootObject, rootObject, new HashSet(), referenceResolver).ConfigureAwait(false); + var updater = new JsonReferenceUpdater(rootObject, referenceResolver); + await updater.VisitAsync(rootObject).ConfigureAwait(false); } /// Converts JSON references ($ref) to property references. @@ -46,12 +43,23 @@ public static string ConvertPropertyReferences(string data) } /// Updates the properties - /// from the available properties. + /// from the available properties with inlining external references. /// The root object. public static void UpdateSchemaReferencePaths(object rootObject) + { + UpdateSchemaReferencePaths(rootObject, false); + } + + /// Updates the properties + /// from the available properties. + /// The root object. + /// Specifies whether to remove external references (otherwise they are inlined). + public static void UpdateSchemaReferencePaths(object rootObject, bool removeExternalReferences) { var schemaReferences = new Dictionary(); - UpdateSchemaReferencePaths(rootObject, new HashSet(), schemaReferences); + + var updater = new JsonReferencePathUpdater(rootObject, schemaReferences, removeExternalReferences); + updater.VisitAsync(rootObject).GetAwaiter().GetResult(); var searchedSchemas = schemaReferences.Select(p => p.Value).Distinct(); var result = JsonPathUtilities.GetJsonPaths(rootObject, searchedSchemas); @@ -60,91 +68,88 @@ public static void UpdateSchemaReferencePaths(object rootObject) p.Key.ReferencePath = result[p.Value]; } - private static void UpdateSchemaReferencePaths(object obj, HashSet checkedObjects, Dictionary schemaReferences) + private class JsonReferenceUpdater : JsonSchemaVisitor { - if (obj == null || obj is string) - return; + private bool _inlineDefinitionsRound; + private readonly object _rootObject; + private readonly JsonReferenceResolver _referenceResolver; - var schema = obj as IJsonReference; - if (schema != null && schema.Reference != null) + public JsonReferenceUpdater(object rootObject, JsonReferenceResolver referenceResolver) { - if (schema.Reference.DocumentPath == null) - schemaReferences[schema] = schema.Reference.ActualObject; - else - { - // TODO: Improve performance here (like the rest) - var externalReference = schema.Reference; - var externalReferenceRoot = externalReference.FindRootParent(); - schema.ReferencePath = externalReference.DocumentPath + JsonPathUtilities.GetJsonPath(externalReferenceRoot, externalReference); - } + _rootObject = rootObject; + _referenceResolver = referenceResolver; } - if (obj is IDictionary) - { - foreach (var item in ((IDictionary)obj).Values.OfType().ToList()) - UpdateSchemaReferencePaths(item, checkedObjects, schemaReferences); - } - else if (obj is IEnumerable) + public override async Task VisitAsync(object obj) { - foreach (var item in ((IEnumerable)obj).OfType().ToArray()) - UpdateSchemaReferencePaths(item, checkedObjects, schemaReferences); + _inlineDefinitionsRound = true; + await base.VisitAsync(obj); + + _inlineDefinitionsRound = false; + await base.VisitAsync(obj); } - if (!(obj is JToken)) + protected override async Task VisitJsonReferenceAsync(IJsonReference reference, string path, string typeNameHint) { - foreach (var member in ReflectionCache.GetPropertiesAndFields(obj.GetType()).Where(p => - p.CanRead && p.IsIndexer == false && p.MemberInfo is PropertyInfo && - p.CustomAttributes.JsonIgnoreAttribute == null)) + if (reference.ReferencePath != null && reference.Reference == null) { - var value = member.GetValue(obj); - if (value != null) + if (_inlineDefinitionsRound) { - if (!checkedObjects.Contains(value)) + // inline $refs in "definitions" + if (path.EndsWith("/definitions/" + typeNameHint)) { - checkedObjects.Add(value); - UpdateSchemaReferencePaths(value, checkedObjects, schemaReferences); + return await _referenceResolver + .ResolveReferenceWithoutAppendAsync(_rootObject, reference.ReferencePath) + .ConfigureAwait(false); } } + else + { + // load $refs and add them to "definitions" + reference.Reference = await _referenceResolver + .ResolveReferenceAsync(_rootObject, reference.ReferencePath) + .ConfigureAwait(false); + } } + + return reference; } } - private static async Task UpdateSchemaReferencesAsync(object rootObject, object obj, HashSet checkedObjects, JsonReferenceResolver jsonReferenceResolver) + private class JsonReferencePathUpdater : JsonSchemaVisitor { - if (obj == null || obj is string) - return; - - var schema = obj as IJsonReference; - if (schema != null && schema.ReferencePath != null) - schema.Reference = await jsonReferenceResolver.ResolveReferenceAsync(rootObject, schema.ReferencePath).ConfigureAwait(false); + private readonly object _rootObject; + private readonly Dictionary _schemaReferences; + private readonly bool _removeExternalReferences; - if (obj is IDictionary) - { - foreach (var item in ((IDictionary)obj).Values.OfType().ToArray()) - await UpdateSchemaReferencesAsync(rootObject, item, checkedObjects, jsonReferenceResolver).ConfigureAwait(false); - } - else if (obj is IEnumerable) + public JsonReferencePathUpdater(object rootObject, Dictionary schemaReferences, bool removeExternalReferences) { - foreach (var item in ((IEnumerable)obj).OfType().ToArray()) - await UpdateSchemaReferencesAsync(rootObject, item, checkedObjects, jsonReferenceResolver).ConfigureAwait(false); + _rootObject = rootObject; + _schemaReferences = schemaReferences; + _removeExternalReferences = removeExternalReferences; } - if (!(obj is JToken)) +#pragma warning disable 1998 + protected override async Task VisitJsonReferenceAsync(IJsonReference reference, string path, string typeNameHint) +#pragma warning restore 1998 { - foreach (var property in ReflectionCache.GetPropertiesAndFields(obj.GetType()).Where(p => - p.CanRead && p.IsIndexer == false && p.MemberInfo is PropertyInfo && - p.CustomAttributes.JsonIgnoreAttribute == null)) + if (reference.Reference != null) { - var value = property.GetValue(obj); - if (value != null) + if (!_removeExternalReferences || reference.Reference.DocumentPath == null) + _schemaReferences[reference] = reference.Reference.ActualObject; + else { - if (!checkedObjects.Contains(value)) - { - checkedObjects.Add(value); - await UpdateSchemaReferencesAsync(rootObject, value, checkedObjects, jsonReferenceResolver).ConfigureAwait(false); - } + var externalReference = reference.Reference; + var externalReferenceRoot = externalReference.FindParentDocument(); + reference.ReferencePath = externalReference.DocumentPath + JsonPathUtilities.GetJsonPath(externalReferenceRoot, externalReference).TrimEnd('#'); } } + else if (_removeExternalReferences && _rootObject != reference && reference.DocumentPath != null) + { + return null; + } + + return reference; } } } diff --git a/src/NJsonSchema/JsonSchemaVisitor.cs b/src/NJsonSchema/JsonSchemaVisitor.cs index 03313303d..cf1149d03 100644 --- a/src/NJsonSchema/JsonSchemaVisitor.cs +++ b/src/NJsonSchema/JsonSchemaVisitor.cs @@ -6,8 +6,10 @@ // Rico Suter, mail@rsuter.com //----------------------------------------------------------------------- +using System; using System.Collections; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; using NJsonSchema.Infrastructure; @@ -21,9 +23,9 @@ public abstract class JsonSchemaVisitor /// Processes an object. /// The object to process. /// The task. - public async Task VisitAsync(object obj) + public virtual async Task VisitAsync(object obj) { - await VisitAsync(obj, "#", null, new HashSet()); + await VisitAsync(obj, "#", null, new HashSet(), o => throw new NotSupportedException("Cannot replace the root.")); } /// Processes an object. @@ -31,79 +33,140 @@ public async Task VisitAsync(object obj) /// The path /// The type name hint. /// The checked objects. + /// The replacer. /// The task. - protected virtual async Task VisitAsync(object obj, string path, string typeNameHint, ISet checkedObjects) + protected virtual async Task VisitAsync(object obj, string path, string typeNameHint, ISet checkedObjects, Action replacer) { - if (checkedObjects.Contains(obj)) + if (obj == null || checkedObjects.Contains(obj)) return; checkedObjects.Add(obj); - if (obj is IJsonReference reference) - { - await VisitJsonReferenceAsync(reference, path); - } if (obj is JsonSchema4 schema) { - await VisitSchemaAsync(schema, path, typeNameHint); + var newSchema = await VisitSchemaAsync(schema, path, typeNameHint); + if (newSchema != schema) + { + replacer(newSchema); + schema = newSchema; + } + + var newReference = await VisitJsonReferenceAsync(schema, path, typeNameHint); + if (newReference != schema) + { + replacer(newReference); + schema = (JsonSchema4)newReference; + } + + if (schema == null) + return; if (schema.AdditionalItemsSchema != null) - await VisitAsync(schema.AdditionalItemsSchema, path + "/additionalItems", null, checkedObjects); + await VisitAsync(schema.AdditionalItemsSchema, path + "/additionalItems", null, checkedObjects, o => schema.AdditionalItemsSchema = (JsonSchema4)o); if (schema.AdditionalPropertiesSchema != null) - await VisitAsync(schema.AdditionalPropertiesSchema, path + "/additionalProperties", null, checkedObjects); + await VisitAsync(schema.AdditionalPropertiesSchema, path + "/additionalProperties", null, checkedObjects, o => schema.AdditionalPropertiesSchema = (JsonSchema4)o); if (schema.Item != null) - await VisitAsync(schema.Item, path + "/items", null, checkedObjects); + await VisitAsync(schema.Item, path + "/items", null, checkedObjects, o => schema.Item = (JsonSchema4)o); for (var i = 0; i < schema.Items.Count; i++) - await VisitAsync(schema.Items.ElementAt(i), path + "/items[" + i + "]", null, checkedObjects); + { + var index = i; + await VisitAsync(schema.Items.ElementAt(i), path + "/items[" + i + "]", null, checkedObjects, o => ReplaceOrDelete(schema.Items, index, (JsonSchema4)o)); + } for (var i = 0; i < schema.AllOf.Count; i++) - await VisitAsync(schema.AllOf.ElementAt(i), path + "/allOf[" + i + "]", null, checkedObjects); + { + var index = i; + await VisitAsync(schema.AllOf.ElementAt(i), path + "/allOf[" + i + "]", null, checkedObjects, o => ReplaceOrDelete(schema.AllOf, index, (JsonSchema4)o)); + } for (var i = 0; i < schema.AnyOf.Count; i++) - await VisitAsync(schema.AnyOf.ElementAt(i), path + "/anyOf[" + i + "]", null, checkedObjects); + { + var index = i; + await VisitAsync(schema.AnyOf.ElementAt(i), path + "/anyOf[" + i + "]", null, checkedObjects, o => ReplaceOrDelete(schema.AnyOf, index, (JsonSchema4)o)); + } for (var i = 0; i < schema.OneOf.Count; i++) - await VisitAsync(schema.OneOf.ElementAt(i), path + "/oneOf[" + i + "]", null, checkedObjects); + { + var index = i; + await VisitAsync(schema.OneOf.ElementAt(i), path + "/oneOf[" + i + "]", null, checkedObjects, o => ReplaceOrDelete(schema.OneOf, index, (JsonSchema4)o)); + } if (schema.Not != null) - await VisitAsync(schema.Not, path + "/not", null, checkedObjects); - - foreach (var p in schema.Properties) - await VisitAsync(p.Value, path + "/properties/" + p.Key, p.Key, checkedObjects); + await VisitAsync(schema.Not, path + "/not", null, checkedObjects, o => schema.Not = (JsonSchema4)o); - foreach (var p in schema.PatternProperties) - await VisitAsync(p.Value, path + "/patternProperties/" + p.Key, null, checkedObjects); + foreach (var p in schema.Properties.ToArray()) + await VisitAsync(p.Value, path + "/properties/" + p.Key, p.Key, checkedObjects, o => schema.Properties[p.Key] = (JsonProperty)o); - foreach (var p in schema.Definitions) - await VisitAsync(p.Value, path + "/definitions/" + p.Key, p.Key, checkedObjects); - } - else if (obj != null && !(obj is string) && !checkedObjects.Contains(obj)) - { - // Reflection fallback + foreach (var p in schema.PatternProperties.ToArray()) + await VisitAsync(p.Value, path + "/patternProperties/" + p.Key, null, checkedObjects, o => schema.PatternProperties[p.Key] = (JsonProperty)o); - if (obj is IDictionary dictionary) + foreach (var p in schema.Definitions.ToArray()) { - foreach (var key in dictionary.Keys) - await VisitAsync(dictionary[key], path + "/" + key, key.ToString(), checkedObjects); + await VisitAsync(p.Value, path + "/definitions/" + p.Key, p.Key, checkedObjects, o => + { + if (o != null) + schema.Definitions[p.Key] = (JsonSchema4)o; + else + schema.Definitions.Remove(p.Key); + }); } - else if (obj is IEnumerable enumerable) + } + else + { + if (obj is IJsonReference reference) { - var i = 0; - foreach (var item in enumerable) + var newReference = await VisitJsonReferenceAsync(reference, path, typeNameHint); + if (newReference != reference) { - await VisitAsync(item, path + "[" + i + "]", null, checkedObjects); - i++; + replacer(newReference); + obj = newReference; } + + if (obj == null) + return; } - else + + if (!(obj is string)) { - foreach (var member in ReflectionCache.GetPropertiesAndFields(obj.GetType())) + // Reflection fallback + if (obj is IDictionary dictionary) + { + foreach (var key in dictionary.Keys.OfType().ToArray()) + { + await VisitAsync(dictionary[key], path + "/" + key, key.ToString(), checkedObjects, o => + { + if (o != null) + dictionary[key] = (JsonSchema4)o; + else + dictionary.Remove(key); + }); + } + } + else if (obj is IList list) + { + var items = list.OfType().ToArray(); + for (var i = 0; i < items.Length; i++) + { + var index = i; + await VisitAsync(items[i], path + "[" + i + "]", null, checkedObjects, o => ReplaceOrDelete(list, index, o)); + } + } + else if (obj is IEnumerable enumerable) { - var value = member.GetValue(obj); - if (value != null) - await VisitAsync(value, path + "/" + member.GetName(), member.GetName(), checkedObjects); + var items = enumerable.OfType().ToArray(); + for (var i = 0; i < items.Length; i++) + await VisitAsync(items[i], path + "[" + i + "]", null, checkedObjects, o => throw new NotSupportedException("Cannot replace enumerable item.")); + } + else + { + foreach (var member in ReflectionCache.GetPropertiesAndFields(obj.GetType())) + { + var value = member.GetValue(obj); + if (value != null) + await VisitAsync(value, path + "/" + member.GetName(), member.GetName(), checkedObjects, o => member.SetValue(obj, o)); + } } } } @@ -115,21 +178,36 @@ protected virtual async Task VisitAsync(object obj, string path, string typeName /// The type name hint. /// The task. #pragma warning disable 1998 - protected virtual async Task VisitSchemaAsync(JsonSchema4 schema, string path, string typeNameHint) + protected virtual async Task VisitSchemaAsync(JsonSchema4 schema, string path, string typeNameHint) #pragma warning restore 1998 { - // must be empty + return schema; } /// Called when a is visited. /// The visited schema. /// The path. + /// The type name hint. /// The task. #pragma warning disable 1998 - protected virtual async Task VisitJsonReferenceAsync(IJsonReference reference, string path) + protected virtual async Task VisitJsonReferenceAsync(IJsonReference reference, string path, string typeNameHint) #pragma warning restore 1998 { - // must be empty + return reference; + } + + private void ReplaceOrDelete(ICollection collection, int index, T obj) + { + ((Collection)collection).RemoveAt(index); + if (obj != null) + ((Collection)collection).Insert(index, obj); + } + + private void ReplaceOrDelete(IList collection, int index, object obj) + { + collection.RemoveAt(index); + if (obj != null) + collection.Insert(index, obj); } } } diff --git a/src/NJsonSchema/References/JsonReferenceBase.cs b/src/NJsonSchema/References/JsonReferenceBase.cs index 20d7833ae..18e721a86 100644 --- a/src/NJsonSchema/References/JsonReferenceBase.cs +++ b/src/NJsonSchema/References/JsonReferenceBase.cs @@ -23,7 +23,7 @@ public abstract class JsonReferenceBase : IJsonReferenceBase /// Gets or sets the type reference path ($ref). [JsonProperty(JsonPathUtilities.ReferenceReplaceString, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] - public string ReferencePath { get; set; } + string IJsonReferenceBase.ReferencePath { get; set; } /// Gets or sets the referenced object. [JsonIgnore] @@ -35,7 +35,7 @@ public virtual T Reference if (_reference != value) { _reference = value; - ReferencePath = null; + ((IJsonReferenceBase)this).ReferencePath = null; } } } diff --git a/src/NJsonSchema/References/JsonReferenceExtensions.cs b/src/NJsonSchema/References/JsonReferenceExtensions.cs index 100d22c13..a3d1bbd8a 100644 --- a/src/NJsonSchema/References/JsonReferenceExtensions.cs +++ b/src/NJsonSchema/References/JsonReferenceExtensions.cs @@ -13,14 +13,21 @@ public static class JsonReferenceExtensions { /// Finds the root parent of this schema. /// The parent schema or this when this is the root. - public static object FindRootParent(this IJsonReference obj) + public static object FindParentDocument(this IJsonReference obj) { + if (obj.DocumentPath != null) + return obj; + var parent = obj.PossibleRoot; if (parent == null) return obj; while ((parent as IJsonReference)?.PossibleRoot != null) + { parent = ((IJsonReference)parent).PossibleRoot; + if (parent is IDocumentPathProvider pathProvider && pathProvider.DocumentPath != null) + return parent; + } return parent; } From 0f8e0f3deee0d38900777806a74cff862a403a60 Mon Sep 17 00:00:00 2001 From: Rico Suter Date: Mon, 16 Oct 2017 19:49:41 +0200 Subject: [PATCH 18/19] v9.8.0 --- .../NJsonSchema.CodeGeneration.CSharp.csproj | 2 +- .../NJsonSchema.CodeGeneration.TypeScript.csproj | 2 +- .../NJsonSchema.CodeGeneration.csproj | 2 +- src/NJsonSchema/NJsonSchema.csproj | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/NJsonSchema.CodeGeneration.CSharp/NJsonSchema.CodeGeneration.CSharp.csproj b/src/NJsonSchema.CodeGeneration.CSharp/NJsonSchema.CodeGeneration.CSharp.csproj index a05507430..d81ba8065 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/NJsonSchema.CodeGeneration.CSharp.csproj +++ b/src/NJsonSchema.CodeGeneration.CSharp/NJsonSchema.CodeGeneration.CSharp.csproj @@ -2,7 +2,7 @@ netstandard1.3;net451 JSON Schema draft v4 reader, generator and validator for .NET - 9.7.7 + 9.8.0 json schema validation generator .net Copyright © Rico Suter, 2017 https://github.com/rsuter/NJsonSchema/blob/master/LICENSE.md diff --git a/src/NJsonSchema.CodeGeneration.TypeScript/NJsonSchema.CodeGeneration.TypeScript.csproj b/src/NJsonSchema.CodeGeneration.TypeScript/NJsonSchema.CodeGeneration.TypeScript.csproj index c0483f265..e6025ae5d 100644 --- a/src/NJsonSchema.CodeGeneration.TypeScript/NJsonSchema.CodeGeneration.TypeScript.csproj +++ b/src/NJsonSchema.CodeGeneration.TypeScript/NJsonSchema.CodeGeneration.TypeScript.csproj @@ -2,7 +2,7 @@ netstandard1.3;net451 JSON Schema draft v4 reader, generator and validator for .NET - 9.7.7 + 9.8.0 json schema validation generator .net Copyright © Rico Suter, 2017 https://github.com/rsuter/NJsonSchema/blob/master/LICENSE.md diff --git a/src/NJsonSchema.CodeGeneration/NJsonSchema.CodeGeneration.csproj b/src/NJsonSchema.CodeGeneration/NJsonSchema.CodeGeneration.csproj index b969c8179..d95567344 100644 --- a/src/NJsonSchema.CodeGeneration/NJsonSchema.CodeGeneration.csproj +++ b/src/NJsonSchema.CodeGeneration/NJsonSchema.CodeGeneration.csproj @@ -2,7 +2,7 @@ netstandard1.3;net451 JSON Schema draft v4 reader, generator and validator for .NET - 9.7.7 + 9.8.0 json schema validation generator .net Copyright © Rico Suter, 2017 https://github.com/rsuter/NJsonSchema/blob/master/LICENSE.md diff --git a/src/NJsonSchema/NJsonSchema.csproj b/src/NJsonSchema/NJsonSchema.csproj index 4180a3fa2..e59a19111 100644 --- a/src/NJsonSchema/NJsonSchema.csproj +++ b/src/NJsonSchema/NJsonSchema.csproj @@ -2,8 +2,8 @@ netstandard1.0;net40;net45 - JSON Schema draft v4 reader, generator and validator for .NET - 9.7.7 + JON Schema draft v4 reader, generator and validator for .NET + 9.8.0 json schema validation generator .net Copyright © Rico Suter, 2017 https://github.com/rsuter/NJsonSchema/blob/master/LICENSE.md From 36292c20821a591999324b3786fed20523029013 Mon Sep 17 00:00:00 2001 From: Rico Suter Date: Mon, 16 Oct 2017 19:57:38 +0200 Subject: [PATCH 19/19] Improved performance --- .../JsonSchemaReferenceUtilities.cs | 21 ++++-------------- src/NJsonSchema/JsonSchemaVisitor.cs | 22 +++++++++---------- 2 files changed, 15 insertions(+), 28 deletions(-) diff --git a/src/NJsonSchema/JsonSchemaReferenceUtilities.cs b/src/NJsonSchema/JsonSchemaReferenceUtilities.cs index 76450e41c..db7528930 100644 --- a/src/NJsonSchema/JsonSchemaReferenceUtilities.cs +++ b/src/NJsonSchema/JsonSchemaReferenceUtilities.cs @@ -70,7 +70,6 @@ public static void UpdateSchemaReferencePaths(object rootObject, bool removeExte private class JsonReferenceUpdater : JsonSchemaVisitor { - private bool _inlineDefinitionsRound; private readonly object _rootObject; private readonly JsonReferenceResolver _referenceResolver; @@ -80,28 +79,16 @@ public JsonReferenceUpdater(object rootObject, JsonReferenceResolver referenceRe _referenceResolver = referenceResolver; } - public override async Task VisitAsync(object obj) - { - _inlineDefinitionsRound = true; - await base.VisitAsync(obj); - - _inlineDefinitionsRound = false; - await base.VisitAsync(obj); - } - protected override async Task VisitJsonReferenceAsync(IJsonReference reference, string path, string typeNameHint) { if (reference.ReferencePath != null && reference.Reference == null) { - if (_inlineDefinitionsRound) + if (path.EndsWith("/definitions/" + typeNameHint)) { // inline $refs in "definitions" - if (path.EndsWith("/definitions/" + typeNameHint)) - { - return await _referenceResolver - .ResolveReferenceWithoutAppendAsync(_rootObject, reference.ReferencePath) - .ConfigureAwait(false); - } + return await _referenceResolver + .ResolveReferenceWithoutAppendAsync(_rootObject, reference.ReferencePath) + .ConfigureAwait(false); } else { diff --git a/src/NJsonSchema/JsonSchemaVisitor.cs b/src/NJsonSchema/JsonSchemaVisitor.cs index cf1149d03..7f42ff867 100644 --- a/src/NJsonSchema/JsonSchemaVisitor.cs +++ b/src/NJsonSchema/JsonSchemaVisitor.cs @@ -60,6 +60,17 @@ protected virtual async Task VisitAsync(object obj, string path, string typeName if (schema == null) return; + foreach (var p in schema.Definitions.ToArray()) + { + await VisitAsync(p.Value, path + "/definitions/" + p.Key, p.Key, checkedObjects, o => + { + if (o != null) + schema.Definitions[p.Key] = (JsonSchema4)o; + else + schema.Definitions.Remove(p.Key); + }); + } + if (schema.AdditionalItemsSchema != null) await VisitAsync(schema.AdditionalItemsSchema, path + "/additionalItems", null, checkedObjects, o => schema.AdditionalItemsSchema = (JsonSchema4)o); @@ -101,17 +112,6 @@ protected virtual async Task VisitAsync(object obj, string path, string typeName foreach (var p in schema.PatternProperties.ToArray()) await VisitAsync(p.Value, path + "/patternProperties/" + p.Key, null, checkedObjects, o => schema.PatternProperties[p.Key] = (JsonProperty)o); - - foreach (var p in schema.Definitions.ToArray()) - { - await VisitAsync(p.Value, path + "/definitions/" + p.Key, p.Key, checkedObjects, o => - { - if (o != null) - schema.Definitions[p.Key] = (JsonSchema4)o; - else - schema.Definitions.Remove(p.Key); - }); - } } else {