From 83452fea08e19f16b114c6495ea1c4e6797f3fd1 Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Thu, 25 Mar 2021 13:26:59 +0200 Subject: [PATCH] Replace DotLiquid with Fluid * upgrade libs * reduce warnings to see the forest from the trees * optimize OrderByBaseDependency not to capture with lambdas and re-calculate indexes * optimize GetOrGenerateTypeName * use separate content loader to reduce re-loading from assemblies/disk * support child scopes when calling templates * write tabified version directly to writer * make template rewrite more deterministic, only add tab parameter if needed * improve tabCount logic * faster member segment traversal * ignore Subtypes_are_serialized_with_correct_discriminator on net461 * fix invalid Liquid, elseif should be elsif, invalid if conditions --- .gitignore | 1 + .../NJsonSchema.Benchmark.csproj | 14 +- src/NJsonSchema.Benchmark/Program.cs | 15 +- .../AllOfTests.cs | 4 +- ...nSchema.CodeGeneration.CSharp.Tests.csproj | 47 +-- .../NullableReferenceTypesTests.cs | 6 +- .../NJsonSchema.CodeGeneration.CSharp.csproj | 6 +- .../Templates/Class.Constructor.Record.liquid | 30 +- .../Templates/Class.FromJson.liquid | 10 +- .../Templates/Class.Inheritance.liquid | 14 +- .../Templates/Class.Inpc.liquid | 2 +- .../Templates/Class.ToJson.liquid | 10 +- .../Templates/Class.liquid | 167 ++++++----- .../Templates/DateFormatConverter.liquid | 6 +- .../Templates/Enum.liquid | 22 +- .../Templates/File.liquid | 4 +- .../Templates/JsonInheritanceConverter.liquid | 8 +- .../InheritanceSerializationTests.cs | 12 +- .../LiquidTests.cs | 26 +- .../NJsonSchema.CodeGeneration.Tests.csproj | 23 +- .../DictionaryTests.cs | 8 +- ...ema.CodeGeneration.TypeScript.Tests.csproj | 18 +- .../Models/PropertyModel.cs | 2 +- ...sonSchema.CodeGeneration.TypeScript.csproj | 3 +- .../Templates/Class.liquid | 19 +- .../Templates/ConvertToClass.liquid | 12 +- .../Templates/ConvertToJavaScript.liquid | 44 +-- .../Templates/Enum.StringLiteral.liquid | 6 +- .../Templates/Enum.liquid | 8 +- .../Templates/File.liquid | 16 +- .../Templates/Interface.liquid | 18 +- .../Templates/KnockoutClass.liquid | 102 +++---- .../CodeArtifactExtensions.cs | 30 +- .../DefaultTemplateFactory.cs | 280 +++++++++++------- .../LiquidProxyHash.cs | 134 --------- .../NJsonSchema.CodeGeneration.csproj | 14 +- .../TypeResolverBase.cs | 13 +- .../NJsonSchema.Demo.Performance.csproj | 2 +- src/NJsonSchema.Demo/NJsonSchema.Demo.csproj | 99 +------ .../Conversion/TypeToSchemaTests.cs | 2 +- .../Generation/ContractResolverTests.cs | 2 - .../JsonInheritanceAttribute.cs | 2 +- .../JsonInheritanceConverter.cs | 2 +- .../JsonInheritanceConverterAttribute.cs | 2 +- ...temTextJsonExtensionDataGenerationTests.cs | 2 +- .../SystemTextJsonInheritanceTests.cs | 2 +- .../SystemTextJsonOptionsConverterTests.cs | 2 +- .../NJsonSchema.Tests.csproj | 59 +--- .../Validation/NumberTests.cs | 2 +- .../NJsonSchema.Yaml.Tests.csproj | 70 +---- .../IJsonSchemaExtensionDataAttribute.cs | 2 +- .../Annotations/ItemsCanBeNullAttribute.cs | 3 +- .../Annotations/JsonSchemaAttribute.cs | 3 +- .../JsonSchemaExtensionDataAttribute.cs | 4 +- .../Annotations/JsonSchemaFlattenAttribute.cs | 2 +- .../Annotations/JsonSchemaTypeAttribute.cs | 4 +- .../Annotations/MultipleOfAttribute.cs | 4 +- .../Collections/ObservableDictionary.cs | 72 ++--- src/NJsonSchema/ConversionUtilities.cs | 153 ++++++++-- .../Generation/DefaultSchemaNameGenerator.cs | 6 +- .../Generation/JsonSchemaGenerator.cs | 21 +- .../Generation/SystemTextJsonUtilities.cs | 20 +- src/NJsonSchema/JsonPathUtilities.cs | 20 +- src/NJsonSchema/JsonSchema.cs | 10 +- .../Visitors/JsonReferenceVisitorBase.cs | 4 +- 65 files changed, 782 insertions(+), 948 deletions(-) delete mode 100644 src/NJsonSchema.CodeGeneration/LiquidProxyHash.cs diff --git a/.gitignore b/.gitignore index 9cbf81e8d..ff2c52006 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ src/packages/Newtonsoft.Json** /src/TestResults /src/.cr/* /src/NJsonSchema.Benchmark/BenchmarkDotNet.Artifacts* +_ReSharper.Caches diff --git a/src/NJsonSchema.Benchmark/NJsonSchema.Benchmark.csproj b/src/NJsonSchema.Benchmark/NJsonSchema.Benchmark.csproj index 3163ab893..aa05aa7ff 100644 --- a/src/NJsonSchema.Benchmark/NJsonSchema.Benchmark.csproj +++ b/src/NJsonSchema.Benchmark/NJsonSchema.Benchmark.csproj @@ -2,21 +2,21 @@ net5.0 false + $(NoWarn),xUnit1013 - - + + - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + + diff --git a/src/NJsonSchema.Benchmark/Program.cs b/src/NJsonSchema.Benchmark/Program.cs index 1546385ae..28d65dae0 100644 --- a/src/NJsonSchema.Benchmark/Program.cs +++ b/src/NJsonSchema.Benchmark/Program.cs @@ -1,10 +1,13 @@ +using System; +using System.Runtime.CompilerServices; + namespace NJsonSchema.Benchmark { public static class Program { public static void Main(string[] args) { - // RunCsharpBenchmark(); + //RunCsharpBenchmark(); BenchmarkDotNet.Running.BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).RunAllJoined(); } @@ -13,6 +16,16 @@ private static void RunCsharpBenchmark() var benchmark = new CsharpGeneratorBenchmark(); benchmark.Setup().GetAwaiter().GetResult(); benchmark.GenerateFile(); + ExecuteBenchmarkMultiple(benchmark.GenerateFile); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ExecuteBenchmarkMultiple(Action a) + { + for (int i = 0; i < 100; ++i) + { + a(); + } } } } \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration.CSharp.Tests/AllOfTests.cs b/src/NJsonSchema.CodeGeneration.CSharp.Tests/AllOfTests.cs index fe37d09de..fc4da336e 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp.Tests/AllOfTests.cs +++ b/src/NJsonSchema.CodeGeneration.CSharp.Tests/AllOfTests.cs @@ -193,11 +193,11 @@ public async Task When_more_properties_are_defined_in_allOf_and_type_none_then_a var code = generator.GenerateFile("Foo").Replace("\r\n", "\n"); //// Assert - Assert.Contains(@" public partial class Foo : Anonymous + Assert.Contains(@" public partial class Foo : Anonymous { [Newtonsoft.Json.JsonProperty(""prop1"", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public string Prop1 { get; set; } - + [Newtonsoft.Json.JsonProperty(""prop2"", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] public string Prop2 { get; set; } ".Replace("\r", string.Empty), code); diff --git a/src/NJsonSchema.CodeGeneration.CSharp.Tests/NJsonSchema.CodeGeneration.CSharp.Tests.csproj b/src/NJsonSchema.CodeGeneration.CSharp.Tests/NJsonSchema.CodeGeneration.CSharp.Tests.csproj index e484a4136..aef37869f 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp.Tests/NJsonSchema.CodeGeneration.CSharp.Tests.csproj +++ b/src/NJsonSchema.CodeGeneration.CSharp.Tests/NJsonSchema.CodeGeneration.CSharp.Tests.csproj @@ -1,50 +1,19 @@  - netcoreapp5.0 + net5.0 false true - 1998,1591 + $(NoWarn),1587,1998,1591,618 - - - - - - + - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - - - - - - - - - - - - + + + + + diff --git a/src/NJsonSchema.CodeGeneration.CSharp.Tests/NullableReferenceTypesTests.cs b/src/NJsonSchema.CodeGeneration.CSharp.Tests/NullableReferenceTypesTests.cs index 5291206cf..bca845454 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp.Tests/NullableReferenceTypesTests.cs +++ b/src/NJsonSchema.CodeGeneration.CSharp.Tests/NullableReferenceTypesTests.cs @@ -61,8 +61,8 @@ public async Task When_property_is_optional_and_GenerateNullableOptionalProperti var code = generator.GenerateFile("MyClass"); //// Assert - Assert.Contains("public object? Property { get; set; }= default!;", code); - Assert.Contains("public object Property2 { get; set; }= default!;", code); + Assert.Contains("public object? Property { get; set; } = default!;", code); + Assert.Contains("public object Property2 { get; set; } = default!;", code); } [Fact] @@ -145,7 +145,7 @@ public async Task When_generating_from_json_schema_property_is_optional_and_Gene var code = generator.GenerateFile("MyClass"); //// Assert - Assert.Contains("public object? Property { get; set; }= default!;", code); + Assert.Contains("public object? Property { get; set; } = default!;", code); Assert.Contains("public object Property2 { get; set; } = new object();", code); } } diff --git a/src/NJsonSchema.CodeGeneration.CSharp/NJsonSchema.CodeGeneration.CSharp.csproj b/src/NJsonSchema.CodeGeneration.CSharp/NJsonSchema.CodeGeneration.CSharp.csproj index e6d6dacda..c57e4f5d7 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/NJsonSchema.CodeGeneration.CSharp.csproj +++ b/src/NJsonSchema.CodeGeneration.CSharp/NJsonSchema.CodeGeneration.CSharp.csproj @@ -1,6 +1,6 @@  - netstandard1.3;netstandard2.0;net451 + netstandard2.0;net461 JSON Schema reader, generator and validator for .NET 10.5.2 json schema validation generator .net @@ -17,10 +17,6 @@ bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml - - true - - diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.Constructor.Record.liquid b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.Constructor.Record.liquid index 3977f94cd..5f576efd8 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.Constructor.Record.liquid +++ b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.Constructor.Record.liquid @@ -1,25 +1,25 @@ -{% assign skipComma = true -%} +{%- assign skipComma = true %} {% if HasInheritance %} -{% assign parentProperties = BaseClass.AllProperties -%} +{%- assign parentProperties = BaseClass.AllProperties %} {% else %} -{% assign parentProperties = "" | empty -%} +{%- assign parentProperties = "" | empty %} {% endif %} -{% assign sortedProperties = AllProperties | sort: "Name" -%} -{% assign sortedParentProperties = parentProperties | sort: "Name" -%} +{%- assign sortedProperties = AllProperties | sort: "Name" %} +{%- assign sortedParentProperties = parentProperties | sort: "Name" %} -{% if UseSystemTextJson -%} +{%- if UseSystemTextJson %} [System.Text.Json.Serialization.JsonConstructor] -{% else -%} +{%- else %} [Newtonsoft.Json.JsonConstructor] -{% endif -%} -{% if IsAbstract %}protected{% else %}public{% endif %} {{ClassName}}({% for property in sortedProperties -%}{% if skipComma -%}{% assign skipComma = false %}{% else %}, {% endif -%} {{ property.Type }} @{{ property.Name | lowercamelcase }}{% endfor -%}) -{% assign skipComma = true -%} -{% if HasInheritance -%} - : base({% for property in sortedParentProperties -%}{% if skipComma -%}{% assign skipComma = false %}{% else %}, {% endif -%}{{ property.Name | lowercamelcase }}{% endfor -%}) -{% endif -%} +{%- endif %} +{% if IsAbstract %}protected{% else %}public{% endif %} {{ClassName}}({% for property in sortedProperties %}{%- if skipComma %}{%- assign skipComma = false %}{% else %}, {% endif %}{{ property.Type }} @{{ property.Name | lowercamelcase }}{% endfor %}) +{%- assign skipComma = true %} +{%- if HasInheritance %} + : base({%- for property in sortedParentProperties %}{%- if skipComma %}{%- assign skipComma = false %}{% else %}, {% endif %}{{ property.Name | lowercamelcase }}{%- endfor %}) +{%- endif %} { -{% for property in Properties -%} +{%- for property in Properties %} this.{{property.PropertyName}} = @{{property.Name | lowercamelcase}}; -{% endfor -%} +{%- endfor %} } diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.FromJson.liquid b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.FromJson.liquid index 0aa650d1d..784b6568d 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.FromJson.liquid +++ b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.FromJson.liquid @@ -1,14 +1,14 @@ public static {{ ClassName }} FromJson(string data) { -{% if UseSystemTextJson -%} +{%- if UseSystemTextJson %} var options = {{ JsonSerializerParameterCode }}; -{% if JsonConvertersArrayCode contains "System.Text.Json.Serialization.JsonConverter[]" -%} +{%- if JsonConvertersArrayCode contains "System.Text.Json.Serialization.JsonConverter[]" %} var converters = {{ JsonConvertersArrayCode }}; foreach(var converter in converters) options.Converters.Add(converter); -{% endif -%} +{%- endif %} return System.Text.Json.JsonSerializer.Deserialize<{{ ClassName }}>(data, options); -{% else -%} +{%- else %} return Newtonsoft.Json.JsonConvert.DeserializeObject<{{ ClassName }}>(data, {{ JsonSerializerParameterCode }}); -{% endif -%} +{%- endif %} } \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.Inheritance.liquid b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.Inheritance.liquid index 5e93abd78..7e3ca3080 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.Inheritance.liquid +++ b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.Inheritance.liquid @@ -1,7 +1,7 @@ -{% if RenderInpc -%} -{% if HasInheritance %}: {{ BaseClassName }}{% else %}: System.ComponentModel.INotifyPropertyChanged{% endif %} -{% elsif RenderPrism -%} -{% if HasInheritance %}: {{ BaseClassName }}{% else %}: Prism.Mvvm.BindableBase{% endif %} -{% else -%} -{% if HasInheritance %}: {{ BaseClassName }}{% endif %} -{% endif -%} \ No newline at end of file +{%- if RenderInpc %} +{% if HasInheritance %} : {{ BaseClassName }}{% else %} : System.ComponentModel.INotifyPropertyChanged{% endif %} +{%- elsif RenderPrism %} +{% if HasInheritance %} : {{ BaseClassName }}{% else %} : Prism.Mvvm.BindableBase{% endif %} +{%- else %} +{% if HasInheritance %} : {{ BaseClassName }}{% endif %} +{%- endif %} \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.Inpc.liquid b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.Inpc.liquid index d18c092d7..4ecbc24b8 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.Inpc.liquid +++ b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.Inpc.liquid @@ -4,7 +4,7 @@ public event System.ComponentModel.PropertyChangedEventHandler{% if GenerateNull protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string{% if GenerateNullableReferenceTypes %}?{% endif %} propertyName = null) { var handler = PropertyChanged; - if (handler != null) + if (handler != null) handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); } {% endif -%} diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.ToJson.liquid b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.ToJson.liquid index 691a0d7b0..af0c6a54b 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.ToJson.liquid +++ b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.ToJson.liquid @@ -1,14 +1,14 @@ public string ToJson() { -{% if UseSystemTextJson -%} +{%- if UseSystemTextJson %} var options = {{ JsonSerializerParameterCode }}; -{% if JsonConvertersArrayCode contains "System.Text.Json.Serialization.JsonConverter[]" -%} +{%- if JsonConvertersArrayCode contains "System.Text.Json.Serialization.JsonConverter[]" %} var converters = {{ JsonConvertersArrayCode }}; foreach(var converter in converters) options.Converters.Add(converter); -{% endif -%} +{%- endif %} return System.Text.Json.JsonSerializer.Serialize(this, options); -{% else -%} +{%- else %} return Newtonsoft.Json.JsonConvert.SerializeObject(this, {{ JsonSerializerParameterCode }}); -{% endif -%} +{%- endif %} } \ 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 9aaa91945..5ad606b78 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.liquid +++ b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.liquid @@ -1,103 +1,103 @@ -{% if HasDescription -%} +{%- if HasDescription -%} /// {{ Description | csharpdocs }} -{% endif -%} -{% if HasDiscriminator -%} -{% if UseSystemTextJson -%} +{%- endif -%} +{%- if HasDiscriminator -%} +{%- if UseSystemTextJson -%} [JsonInheritanceConverter(typeof({{ ClassName }}), "{{ Discriminator }}")] -{% else -%} +{%- else -%} [Newtonsoft.Json.JsonConverter(typeof(JsonInheritanceConverter), "{{ Discriminator }}")] -{% endif -%} -{% for derivedClass in DerivedClasses -%} -{% if derivedClass.IsAbstract != true -%} +{%- endif -%} +{%- for derivedClass in DerivedClasses -%} +{%- if derivedClass.IsAbstract != true -%} [JsonInheritanceAttribute("{{ derivedClass.Discriminator }}", typeof({{ derivedClass.ClassName }}))] -{% endif -%} -{% endfor -%} -{% endif -%} +{%- endif -%} +{%- endfor -%} +{%- endif -%} [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "{{ ToolchainVersion }}")] -{% if InheritsExceptionSchema -%} -{% if UseSystemTextJson -%} +{%- if InheritsExceptionSchema -%} +{%- if UseSystemTextJson -%} // TODO(system.text.json): What to do here? -{% else -%} +{%- else -%} [Newtonsoft.Json.JsonObjectAttribute] -{% endif -%} -{% endif -%} -{% if IsDeprecated -%} +{%- endif -%} +{%- endif -%} +{%- if IsDeprecated -%} [System.Obsolete{% if HasDeprecatedMessage %}({{ DeprecatedMessage | literal }}){% endif %}] {% endif -%} -{% template Class.Annotations %} -{{ TypeAccessModifier }} {% if IsAbstract %}abstract {% endif %}partial {% if GenerateNativeRecords %}record{% else %}class{% endif %} {{ClassName}} {% template Class.Inheritance %} +{%- template Class.Annotations %} +{{ TypeAccessModifier }} {% if IsAbstract %}abstract {% endif %}partial {% if GenerateNativeRecords %}record{% else %}class{% endif %} {{ClassName}} {%- template Class.Inheritance %} { -{% if IsTuple -%} - public {{ ClassName }}({% for tupleType in TupleTypes -%}{{ tupleType }} item{{ forloop.index }}{% if forloop.last == false %}, {% endif %}{% endfor -%}) : base({% for tupleType in TupleTypes -%}item{{ forloop.index }}{% if forloop.last == false %}, {% endif %}{% endfor -%}) +{%- if IsTuple -%} + public {{ ClassName }}({%- for tupleType in TupleTypes %}{{ tupleType }} item{{ forloop.index }}{%- if forloop.last == false %}, {% endif %}{% endfor %}) : base({%- for tupleType in TupleTypes %}item{{ forloop.index }}{%- if forloop.last == false %}, {% endif %}{% endfor %}) { } -{% endif -%} -{% if RenderInpc or RenderPrism -%} -{% for property in Properties -%} - private {{ property.Type }} {{ property.FieldName }}{% if property.HasDefaultValue %} = {{ property.DefaultValue }}{% elsif GenerateNullableReferenceTypes -%} = default!{% endif -%}; -{% endfor -%} - -{% endif -%} - {% template Class.Constructor %} -{% if RenderRecord == true -%} - {% template Class.Constructor.Record %} +{%- endif -%} +{%- if RenderInpc or RenderPrism -%} +{%- for property in Properties -%} + private {{ property.Type }} {{ property.FieldName }}{%- if property.HasDefaultValue %} = {{ property.DefaultValue }}{% elsif GenerateNullableReferenceTypes %} = default!{%- endif %}; +{%- endfor -%} -{% endif -%} -{% for property in Properties -%} -{% if property.HasDescription -%} +{%- endif -%} + {%- template Class.Constructor -%} +{%- if RenderRecord -%} + {% template Class.Constructor.Record -%} +{%- endif -%} +{%- for property in Properties -%} +{%- if property.HasDescription -%} /// {{ property.Description | csharpdocs }} -{% endif -%} -{% if UseSystemTextJson -%} +{%- endif -%} +{%- if UseSystemTextJson %} [System.Text.Json.Serialization.JsonPropertyName("{{ property.Name }}")] -{% if property.IsStringEnumArray -%} +{%- if property.IsStringEnumArray %} // TODO(system.text.json): Add string enum item converter -{% endif -%} -{% else -%} +{%- endif -%} +{%- else -%} [Newtonsoft.Json.JsonProperty("{{ property.Name }}", Required = {{ property.JsonPropertyRequiredCode }}{% if property.IsStringEnumArray %}, ItemConverterType = typeof(Newtonsoft.Json.Converters.StringEnumConverter){% endif %})] -{% endif -%} -{% if property.RenderRequiredAttribute -%} +{%- endif -%} +{%- if property.RenderRequiredAttribute -%} [System.ComponentModel.DataAnnotations.Required{% if property.AllowEmptyStrings %}(AllowEmptyStrings = true){% endif %}] -{% endif -%} -{% if property.RenderRangeAttribute -%} +{%- endif -%} +{%- if property.RenderRangeAttribute -%} [System.ComponentModel.DataAnnotations.Range({{ property.RangeMinimumValue }}, {{ property.RangeMaximumValue }})] -{% endif -%} -{% if property.RenderStringLengthAttribute -%} +{%- endif -%} +{%- if property.RenderStringLengthAttribute -%} [System.ComponentModel.DataAnnotations.StringLength({{ property.StringLengthMaximumValue }}{% if property.StringLengthMinimumValue > 0 %}, MinimumLength = {{ property.StringLengthMinimumValue }}{% endif %})] -{% endif -%} -{% if property.RenderMinLengthAttribute -%} +{%- endif -%} +{%- if property.RenderMinLengthAttribute -%} [System.ComponentModel.DataAnnotations.MinLength({{ property.MinLengthAttribute }})] -{% endif -%} -{% if property.RenderMaxLengthAttribute -%} +{%- endif -%} +{%- if property.RenderMaxLengthAttribute -%} [System.ComponentModel.DataAnnotations.MaxLength({{ property.MaxLengthAttribute }})] -{% endif -%} -{% if property.RenderRegularExpressionAttribute -%} +{%- endif -%} +{%- if property.RenderRegularExpressionAttribute -%} [System.ComponentModel.DataAnnotations.RegularExpression(@"{{ property.RegularExpressionValue }}")] -{% endif -%} -{% if property.IsStringEnum -%} -{% if UseSystemTextJson -%} +{%- endif -%} +{%- if property.IsStringEnum -%} +{%- if UseSystemTextJson -%} [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))] -{% else -%} +{%- else -%} [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] -{% endif -%} -{% endif -%} -{% if property.IsDate and UseDateFormatConverter -%} -{% if UseSystemTextJson -%} +{%- endif -%} +{%- endif -%} +{%- if property.IsDate and UseDateFormatConverter -%} +{%- if UseSystemTextJson -%} [System.Text.Json.Serialization.JsonConverter(typeof(DateFormatConverter))] -{% else -%} +{%- else -%} [Newtonsoft.Json.JsonConverter(typeof(DateFormatConverter))] -{% endif -%} -{% endif -%} -{% if property.IsDeprecated -%} +{%- endif -%} +{%- endif -%} +{%- if property.IsDeprecated -%} [System.Obsolete{% if property.HasDeprecatedMessage %}({{ property.DeprecatedMessage | literal }}){% endif %}] -{% endif -%} - {% template Class.Property.Annotations %} - public {{ property.Type }} {{ property.PropertyName }}{% if RenderInpc == false and RenderPrism == false %} { get; {% if property.HasSetter and RenderRecord == false %}set; {% elsif RenderRecord and GenerateNativeRecords %}init; {% endif %}}{% if property.HasDefaultValue and RenderRecord == false %} = {{ property.DefaultValue }};{% elsif GenerateNullableReferenceTypes and RenderRecord == false -%} = default!;{% endif %} +{%- endif -%} + {%- template Class.Property.Annotations -%} + public {{ property.Type }} {{ property.PropertyName }}{% if RenderInpc == false and RenderPrism == false %} { get; {% if property.HasSetter and RenderRecord == false %}set; {% elsif RenderRecord and GenerateNativeRecords %}init; {% endif %}}{% if property.HasDefaultValue and RenderRecord == false %} = {{ property.DefaultValue }};{% elsif GenerateNullableReferenceTypes and RenderRecord == false %} = default!;{% endif %} {% else %} { get { return {{ property.FieldName }}; } -{% if property.HasSetter -%} -{% if RenderInpc -%} + +{%- if property.HasSetter -%} +{%- if RenderInpc -%} {{PropertySetterAccessModifier}}set { if ({{ property.FieldName }} != value) @@ -106,38 +106,37 @@ RaisePropertyChanged(); } } -{% else -%} +{%- else -%} {{PropertySetterAccessModifier}}set { SetProperty(ref {{ property.FieldName }}, value); } -{% endif -%} -{% endif -%} +{%- endif -%} +{%- endif -%} } -{% endif -%} +{%- endif %} +{%- endfor -%} + +{%- if HasAdditionalPropertiesType -%} -{% endfor -%} -{% if HasAdditionalPropertiesType -%} private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); -{% if UseSystemTextJson -%} +{%- if UseSystemTextJson -%} [System.Text.Json.Serialization.JsonExtensionData] -{% else -%} +{%- else -%} [Newtonsoft.Json.JsonExtensionData] -{% endif -%} +{%- endif -%} public System.Collections.Generic.IDictionary AdditionalProperties { get { return _additionalProperties; } {{PropertySetterAccessModifier}}set { _additionalProperties = value; } } -{% endif -%} -{% if GenerateJsonMethods -%} +{%- endif -%} +{%- if GenerateJsonMethods -%} {% template Class.ToJson %} - {% template Class.FromJson %} -{% endif -%} -{% if RenderInpc -%} +{%- endif -%} +{%- if RenderInpc -%} {% template Class.Inpc %} -{% endif -%} - +{%- endif -%} {% template Class.Body %} } diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Templates/DateFormatConverter.liquid b/src/NJsonSchema.CodeGeneration.CSharp/Templates/DateFormatConverter.liquid index c7139cad1..0ab640dc2 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/Templates/DateFormatConverter.liquid +++ b/src/NJsonSchema.CodeGeneration.CSharp/Templates/DateFormatConverter.liquid @@ -1,5 +1,5 @@ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "{{ ToolchainVersion }}")] -{% if UseSystemTextJson -%} +{%- if UseSystemTextJson -%} internal class DateFormatConverter : JsonConverter { public override System.DateTime Read(ref System.Text.Json.Utf8JsonReader reader, System.Type typeToConvert, System.Text.Json.JsonSerializerOptions options) @@ -12,7 +12,7 @@ internal class DateFormatConverter : JsonConverter writer.WriteStringValue(value.ToString("yyyy-MM-dd")); } } -{% else -%} +{%- else -%} internal class DateFormatConverter : Newtonsoft.Json.Converters.IsoDateTimeConverter { public DateFormatConverter() @@ -20,4 +20,4 @@ internal class DateFormatConverter : Newtonsoft.Json.Converters.IsoDateTimeConve DateTimeFormat = "yyyy-MM-dd"; } } -{% endif -%} \ No newline at end of file +{%- endif %} \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Templates/Enum.liquid b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Enum.liquid index 1221ac410..6f74e42e2 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/Templates/Enum.liquid +++ b/src/NJsonSchema.CodeGeneration.CSharp/Templates/Enum.liquid @@ -1,21 +1,21 @@ -{% if HasDescription -%} +{%- if HasDescription -%} /// {{ Description | csharpdocs }} -{% endif -%} +{%- endif -%} [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "{{ ToolchainVersion }}")] -{% if IsEnumAsBitFlags -%} +{%- if IsEnumAsBitFlags -%} [System.Flags] -{% endif -%} +{%- endif -%} {{ TypeAccessModifier }} enum {{ Name }} { -{% for enum in Enums -%} -{% if IsStringEnum -%} +{%- for enum in Enums %} +{%- if IsStringEnum -%} [System.Runtime.Serialization.EnumMember(Value = @"{{ enum.Value | replace: '"', '""' }}")] -{% endif -%} -{% if IsEnumAsBitFlags -%} +{%- endif -%} +{%- if IsEnumAsBitFlags -%} {{ enum.Name }} = {{ enum.InternalFlagValue }}, -{% else -%} +{%- else -%} {{ enum.Name }} = {{ enum.InternalValue }}, -{% endif -%} +{%- endif -%} -{% endfor -%} +{%- endfor %} } \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Templates/File.liquid b/src/NJsonSchema.CodeGeneration.CSharp/Templates/File.liquid index 1e1758d73..59d10aa45 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/Templates/File.liquid +++ b/src/NJsonSchema.CodeGeneration.CSharp/Templates/File.liquid @@ -4,10 +4,10 @@ // //---------------------- -{% if GenerateNullableReferenceTypes -%} +{%- if GenerateNullableReferenceTypes %} #nullable enable -{% endif -%} +{%- endif %} namespace {{ Namespace }} { #pragma warning disable // Disable all warnings diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Templates/JsonInheritanceConverter.liquid b/src/NJsonSchema.CodeGeneration.CSharp/Templates/JsonInheritanceConverter.liquid index d813af3fc..45b336d42 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/Templates/JsonInheritanceConverter.liquid +++ b/src/NJsonSchema.CodeGeneration.CSharp/Templates/JsonInheritanceConverter.liquid @@ -1,5 +1,5 @@ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "{{ ToolchainVersion }}")] -{% if UseSystemTextJson -%} +{%- if UseSystemTextJson -%} internal class JsonInheritanceConverterAttribute : JsonConverterAttribute { public string DiscriminatorName { get; } @@ -114,7 +114,7 @@ internal class JsonInheritanceConverter : System.Text.Json.Serialization. return objectType.Name; } } -{% else -%} +{%- else -%} internal class JsonInheritanceConverter : Newtonsoft.Json.JsonConverter { internal static readonly string DefaultDiscriminatorName = "discriminator"; @@ -193,7 +193,7 @@ internal class JsonInheritanceConverter : Newtonsoft.Json.JsonConverter var discriminatorValue = jObject.GetValue(_discriminator); var discriminator = discriminatorValue != null ? Newtonsoft.Json.Linq.Extensions.Value(discriminatorValue) : null; var subtype = GetObjectSubtype(objectType, discriminator); - + var objectContract = serializer.ContractResolver.ResolveContract(subtype) as Newtonsoft.Json.Serialization.JsonObjectContract; if (objectContract == null || System.Linq.Enumerable.All(objectContract.Properties, p => p.PropertyName != _discriminator)) { @@ -233,4 +233,4 @@ internal class JsonInheritanceConverter : Newtonsoft.Json.JsonConverter return objectType.Name; } } -{% endif -%} \ No newline at end of file +{%- endif -%} \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration.Tests/InheritanceSerializationTests.cs b/src/NJsonSchema.CodeGeneration.Tests/InheritanceSerializationTests.cs index 697391f90..ca0cc2bdb 100644 --- a/src/NJsonSchema.CodeGeneration.Tests/InheritanceSerializationTests.cs +++ b/src/NJsonSchema.CodeGeneration.Tests/InheritanceSerializationTests.cs @@ -282,7 +282,11 @@ public async Task When_schema_contains_discriminator_and_inheritance_hierarchy_t Assert.Contains("this._discriminator = \"Dog\"", code); } +#if NETCORE [Fact] +#else + [Fact(Skip = "Dynamic compilation doesn't work for NET 4.6.1")] +#endif public async Task Subtypes_are_serialized_with_correct_discriminator() { //// Arrange @@ -313,13 +317,6 @@ private Assembly Compile(string code) .WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)) .AddSyntaxTrees(CSharpSyntaxTree.ParseText(code)); -#if NET452 - compilation = compilation.AddReferences( - MetadataReference.CreateFromFile(typeof(object).Assembly.Location), - MetadataReference.CreateFromFile(typeof(JsonConvert).Assembly.Location), - MetadataReference.CreateFromFile(typeof(GeneratedCodeAttribute).Assembly.Location), - MetadataReference.CreateFromFile(typeof(System.Linq.Expressions.Expression).Assembly.Location)); -#else var coreDir = Directory.GetParent(typeof(Enumerable).GetTypeInfo().Assembly.Location); compilation = compilation.AddReferences( MetadataReference.CreateFromFile(typeof(object).Assembly.Location), @@ -332,7 +329,6 @@ private Assembly Compile(string code) MetadataReference.CreateFromFile(coreDir.FullName + Path.DirectorySeparatorChar + "System.ObjectModel.dll"), MetadataReference.CreateFromFile(coreDir.FullName + Path.DirectorySeparatorChar + "System.Linq.Expressions.dll"), MetadataReference.CreateFromFile(coreDir.FullName + Path.DirectorySeparatorChar + "System.Runtime.Extensions.dll")); -#endif using (var stream = new MemoryStream()) { diff --git a/src/NJsonSchema.CodeGeneration.Tests/LiquidTests.cs b/src/NJsonSchema.CodeGeneration.Tests/LiquidTests.cs index d3af47bcc..e02c4211c 100644 --- a/src/NJsonSchema.CodeGeneration.Tests/LiquidTests.cs +++ b/src/NJsonSchema.CodeGeneration.Tests/LiquidTests.cs @@ -1,6 +1,6 @@ -using DotLiquid; -using System.Collections.Generic; +using System.Collections.Generic; using System.Globalization; +using Fluid; using Xunit; namespace NJsonSchema.CodeGeneration.Tests @@ -22,12 +22,13 @@ public void LiquidModelHasDictionry_KeyAccessShouldWork() model.Bar["Baz"] = "abc"; /// Act - var hash = new LiquidProxyHash(model); - var template = Template.Parse("Hi {{ Foo }} {{ Bar[\"Baz\"] }}"); - var text = template.Render(new RenderParameters(CultureInfo.InvariantCulture) + var context = new TemplateContext(model) { - LocalVariables = hash, - }); + CultureInfo = CultureInfo.InvariantCulture + }; + var parser = new FluidParser(); + var template = parser.Parse("Hi {{ Foo }} {{ Bar[\"Baz\"] }}"); + var text = template.Render(context); /// Assert Assert.Equal("Hi Foo. abc", text); @@ -46,12 +47,13 @@ public void LiquidModelHasNestedDictionaryAndLists_KeyAccessAndListIterationShou var liquid = "{% assign x = Bar[\"Baz\"] -%}{% for i in x -%}key1={{ i[\"key1\"] }},key2={{ i[\"key2\"] }}{% endfor -%}"; /// Act - var hash = new LiquidProxyHash(model); - var template = Template.Parse(liquid); - var text = template.Render(new RenderParameters(CultureInfo.InvariantCulture) + var context = new TemplateContext(model) { - LocalVariables = hash, - }); + CultureInfo = CultureInfo.InvariantCulture + }; + var parser = new FluidParser(); + var template = parser.Parse(liquid); + var text = template.Render(context); /// Assert Assert.Equal("key1=value1,key2=value2", text); diff --git a/src/NJsonSchema.CodeGeneration.Tests/NJsonSchema.CodeGeneration.Tests.csproj b/src/NJsonSchema.CodeGeneration.Tests/NJsonSchema.CodeGeneration.Tests.csproj index e6ef4a9b6..b7ffa3d18 100644 --- a/src/NJsonSchema.CodeGeneration.Tests/NJsonSchema.CodeGeneration.Tests.csproj +++ b/src/NJsonSchema.CodeGeneration.Tests/NJsonSchema.CodeGeneration.Tests.csproj @@ -1,22 +1,23 @@  - netcoreapp5.0;net452 + net5.0;net461 True ../NJsonSchema.snk false - 1998,1591 + $(NoWarn),1998,1591,618 - - - - - - + + + + + + + + + + - - - diff --git a/src/NJsonSchema.CodeGeneration.TypeScript.Tests/DictionaryTests.cs b/src/NJsonSchema.CodeGeneration.TypeScript.Tests/DictionaryTests.cs index b176b4644..3ff7f6db5 100644 --- a/src/NJsonSchema.CodeGeneration.TypeScript.Tests/DictionaryTests.cs +++ b/src/NJsonSchema.CodeGeneration.TypeScript.Tests/DictionaryTests.cs @@ -34,7 +34,7 @@ public async Task When_class_inherits_from_any_dictionary_then_interface_has_ind //// Assert Assert.DoesNotContain("extends { [key: string]: any; }", code); - Assert.Contains("[key: string]: any; ", code); + Assert.Contains("[key: string]: any;", code); } [Fact] @@ -55,7 +55,7 @@ public async Task When_class_inherits_from_any_dictionary_then_class_has_indexer //// Assert Assert.DoesNotContain("extends { [key: string]: any; }", code); Assert.DoesNotContain("super()", code); - Assert.Contains("[key: string]: any; ", code); + Assert.Contains("[key: string]: any;", code); } [Fact] @@ -75,7 +75,7 @@ public async Task When_class_inherits_from_string_dictionary_then_interface_has_ //// Assert Assert.DoesNotContain("extends { [key: string]: string; }", code); - Assert.Contains("[key: string]: string | any; ", code); + Assert.Contains("[key: string]: string | any;", code); } [Fact] @@ -96,7 +96,7 @@ public async Task When_class_inherits_from_string_dictionary_then_class_has_inde //// Assert Assert.DoesNotContain("extends { [key: string]: string; }", code); Assert.DoesNotContain("super()", code); - Assert.Contains("[key: string]: string | any; ", code); + Assert.Contains("[key: string]: string | any;", code); } [Fact] diff --git a/src/NJsonSchema.CodeGeneration.TypeScript.Tests/NJsonSchema.CodeGeneration.TypeScript.Tests.csproj b/src/NJsonSchema.CodeGeneration.TypeScript.Tests/NJsonSchema.CodeGeneration.TypeScript.Tests.csproj index b5ea58f31..47309e621 100644 --- a/src/NJsonSchema.CodeGeneration.TypeScript.Tests/NJsonSchema.CodeGeneration.TypeScript.Tests.csproj +++ b/src/NJsonSchema.CodeGeneration.TypeScript.Tests/NJsonSchema.CodeGeneration.TypeScript.Tests.csproj @@ -1,18 +1,20 @@  - netcoreapp5.0;net452 + net5.0;net461 false true - 1998,1591 + $(NoWarn),1587,1998,1591,618 - - - + + + + + - - - + + + diff --git a/src/NJsonSchema.CodeGeneration.TypeScript/Models/PropertyModel.cs b/src/NJsonSchema.CodeGeneration.TypeScript/Models/PropertyModel.cs index 5670f7a58..24d974b1a 100644 --- a/src/NJsonSchema.CodeGeneration.TypeScript/Models/PropertyModel.cs +++ b/src/NJsonSchema.CodeGeneration.TypeScript/Models/PropertyModel.cs @@ -141,7 +141,7 @@ public string ConvertToClassCode Resolver = _resolver, NullValue = _settings.NullValue, Settings = _settings - }); + }).TrimEnd(); } return string.Empty; diff --git a/src/NJsonSchema.CodeGeneration.TypeScript/NJsonSchema.CodeGeneration.TypeScript.csproj b/src/NJsonSchema.CodeGeneration.TypeScript/NJsonSchema.CodeGeneration.TypeScript.csproj index c5a40a48f..03c409b1a 100644 --- a/src/NJsonSchema.CodeGeneration.TypeScript/NJsonSchema.CodeGeneration.TypeScript.csproj +++ b/src/NJsonSchema.CodeGeneration.TypeScript/NJsonSchema.CodeGeneration.TypeScript.csproj @@ -1,6 +1,6 @@  - netstandard1.3;netstandard2.0;net451 + netstandard2.0;net461 JSON Schema reader, generator and validator for .NET 10.5.2 json schema validation generator .net @@ -33,7 +33,6 @@ - diff --git a/src/NJsonSchema.CodeGeneration.TypeScript/Templates/Class.liquid b/src/NJsonSchema.CodeGeneration.TypeScript/Templates/Class.liquid index 46f37bd93..b612ac3f7 100644 --- a/src/NJsonSchema.CodeGeneration.TypeScript/Templates/Class.liquid +++ b/src/NJsonSchema.CodeGeneration.TypeScript/Templates/Class.liquid @@ -6,23 +6,24 @@ {% if property.HasDescription -%} /** {{ property.Description }} */ {% endif -%} - {% if property.IsReadOnly %}readonly {% endif %}{{ property.PropertyName }}{% if property.IsOptional %}?{% elseif RequiresStrictPropertyInitialization && !property.HasDefaultValue %}!{% endif %}: {{ property.Type }}{{ property.TypePostfix }}; + {% if property.IsReadOnly %}readonly {% endif %}{{ property.PropertyName }}{% if property.IsOptional %}?{% elsif RequiresStrictPropertyInitialization %}!{% endif %}: {{ property.Type }}{{ property.TypePostfix }}; {% endfor -%} {% if HasIndexerProperty -%} - [key: string]: {{ IndexerPropertyValueType }}; + [key: string]: {{ IndexerPropertyValueType }}; {% endif -%} {% if HasDiscriminator -%} protected _discriminator: string; {% endif -%} +{% assign condition_temp = HasInheritance == false or ConvertConstructorInterfaceData -%} {% if GenerateConstructorInterface or HasBaseDiscriminator -%} constructor({% if GenerateConstructorInterface %}data?: I{{ ClassName }}{% endif %}) { {% if HasInheritance -%} super({% if GenerateConstructorInterface %}data{% endif %}); {% endif -%} -{% if GenerateConstructorInterface and (HasInheritance == false or ConvertConstructorInterfaceData) -%} +{% if GenerateConstructorInterface and condition_temp -%} if (data) { {% if HasInheritance == false -%} for (var property in data) { @@ -41,7 +42,7 @@ this.{{ property.PropertyName }}[i] = item && !(item).toJSON ? new {{ property.ArrayItemType }}(item) : <{{ property.ArrayItemType }}>item; } } -{% elseif property.IsDictionary -%} +{% elsif property.IsDictionary -%} if (data.{{ property.PropertyName }}) { this.{{ property.PropertyName }} = {}; for (let key in data.{{ property.PropertyName }}) { @@ -52,7 +53,7 @@ } } {% else -%} - this.{{ property.PropertyName }} = data.{{ property.PropertyName }} && !(data.{{ property.PropertyName }}).toJSON ? new {{ property.Type }}(data.{{ property.PropertyName }}) : <{{ property.Type }}>this.{{ property.PropertyName }}; + this.{{ property.PropertyName }} = data.{{ property.PropertyName }} && !(data.{{ property.PropertyName }}).toJSON ? new {{ property.Type }}(data.{{ property.PropertyName }}) : <{{ property.Type }}>this.{{ property.PropertyName }}; {% endif -%} {% endif -%} {% endfor -%} @@ -87,7 +88,7 @@ } {% endif -%} {% for property in Properties -%} - {{ property.ConvertToClassCode | tab }} + {{ property.ConvertToClassCode | strip | tab }} {% endfor -%} } {% endif -%} @@ -144,7 +145,7 @@ } {% endif -%} {% if HasDiscriminator -%} - data["{{ BaseDiscriminator }}"] = this._discriminator; + data["{{ BaseDiscriminator }}"] = this._discriminator; {% endif -%} {% for property in Properties -%} {{ property.ConvertToJavaScriptCode | tab }} @@ -152,7 +153,7 @@ {% if HasInheritance -%} super.toJSON(data); {% endif -%} - return data; + return data; } {% if GenerateCloneMethod -%} @@ -182,7 +183,7 @@ {% endfor -%} {% if HasIndexerProperty -%} - [key: string]: {{ IndexerPropertyValueType }}; + [key: string]: {{ IndexerPropertyValueType }}; {% endif -%} } {% endif -%} \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration.TypeScript/Templates/ConvertToClass.liquid b/src/NJsonSchema.CodeGeneration.TypeScript/Templates/ConvertToClass.liquid index 4ab25e712..2f6241140 100644 --- a/src/NJsonSchema.CodeGeneration.TypeScript/Templates/ConvertToClass.liquid +++ b/src/NJsonSchema.CodeGeneration.TypeScript/Templates/ConvertToClass.liquid @@ -4,7 +4,7 @@ {% else -%} {{ Variable }} = {{ Type }}.fromJS({{ Value }}{% if HandleReferences -%}, _mappings{% endif %}); {% endif -%} -{% elseif IsArray -%} +{% elsif IsArray -%} if (Array.isArray({{ Value }})) { {{ Variable }} = [] as any; for (let item of {{ Value }}) @@ -22,19 +22,19 @@ if (Array.isArray({{ Value }})) { {{ Variable }} = {{ NullValue }}; } {% endif -%} -{% elseif IsDictionary -%} +{% elsif IsDictionary -%} if ({{ Value }}) { {{ Variable }} = {} as any; for (let key in {{ Value }}) { if ({{ Value }}.hasOwnProperty(key)) {% if IsDictionaryValueNewableObject -%} ({{ Variable }}){% if RequiresStrictPropertyInitialization %}!{% endif %}[key] = {{ Value }}[key] ? {{ DictionaryValueType }}.fromJS({{ Value }}[key]{% if HandleReferences %}, _mappings{% endif %}) : {% if HasDictionaryValueDefaultValue %}{{ DictionaryValueDefaultValue }}{% else %}{{ NullValue }}{% endif %}; -{% elseif IsDictionaryValueNewableArray -%} +{% elsif IsDictionaryValueNewableArray -%} ({{ Variable }}){% if RequiresStrictPropertyInitialization %}!{% endif %}[key] = {{ Value }}[key] ? {{ Value }}[key].map((i: any) => {{ DictionaryValueArrayItemType }}.fromJS(i{% if HandleReferences %}, _mappings{% endif %})) : {% if HasDictionaryValueDefaultValue %}{{ DictionaryValueDefaultValue }}{% else %}{{ NullValue }}{% endif %}; -{% elseif IsDictionaryValueDate or IsDictionaryValueDateTime -%} +{% elsif IsDictionaryValueDate or IsDictionaryValueDateTime -%} ({{ Variable }}){% if RequiresStrictPropertyInitialization %}!{% endif %}[key] = {{ Value }}[key] ? {{ StringToDateCode }}({{ Value }}[key].toString()) : {% if HasDictionaryValueDefaultValue %}{{ DictionaryValueDefaultValue }}{% else %}{{ NullValue }}{% endif %}; {% else -%} -{% if(HasDictionaryValueDefaultValue || NullValue != "undefined"){ -%} +{% if HasDictionaryValueDefaultValue or NullValue != "undefined" -%} ({{ Variable }}){% if RequiresStrictPropertyInitialization %}!{% endif %}[key] = {{ Value }}[key] !== undefined ? {{ Value }}[key] : {% if HasDictionaryValueDefaultValue %}{{ DictionaryValueDefaultValue }}{% else %}{{ NullValue }}{% endif %}; {% else -%} ({{ Variable }}){% if RequiresStrictPropertyInitialization %}!{% endif %}[key] = {{ Value }}[key]; @@ -56,4 +56,4 @@ if ({{ Value }}) { {{ Variable }} = {{ Value }}; {% endif -%} {% endif -%} -{% endif -%} +{% endif -%} \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration.TypeScript/Templates/ConvertToJavaScript.liquid b/src/NJsonSchema.CodeGeneration.TypeScript/Templates/ConvertToJavaScript.liquid index ce40e5c06..93102c78c 100644 --- a/src/NJsonSchema.CodeGeneration.TypeScript/Templates/ConvertToJavaScript.liquid +++ b/src/NJsonSchema.CodeGeneration.TypeScript/Templates/ConvertToJavaScript.liquid @@ -1,45 +1,45 @@ -{% if IsNewableObject -%} +{%- if IsNewableObject -%} {{ Variable }} = {{ Value }} ? {{ Value }}.toJSON() : {{ NullValue }}; -{% elseif IsArray -%} +{%- elsif IsArray -%} if (Array.isArray({{ Value }})) { {{ Variable }} = []; for (let item of {{ Value }}) -{% if IsArrayItemNewableObject -%} +{%- if IsArrayItemNewableObject -%} {{ Variable }}.push(item.toJSON()); -{% elseif IsArrayItemDate -%} +{%- elsif IsArrayItemDate -%} {{ Variable }}.push({% if UseJsDate %}formatDate(item){% else %}item.{{ DateToStringCode }}{% endif %}); -{% elseif IsArrayItemDateTime -%} +{%- elsif IsArrayItemDateTime -%} {{ Variable }}.push(item.{{ DateTimeToStringCode }}); -{% else -%} +{%- else -%} {{ Variable }}.push(item); -{% endif -%} +{%- endif -%} } -{% elseif IsDictionary -%} +{%- elsif IsDictionary -%} if ({{ Value }}) { {{ Variable }} = {}; for (let key in {{ Value }}) { if ({{ Value }}.hasOwnProperty(key)) -{% if IsDictionaryValueNewableObject -%} +{%- if IsDictionaryValueNewableObject -%} ({{ Variable }})[key] = {{ Value }}[key] ? {{ Value }}[key].toJSON() : {{ NullValue }}; -{% elseif IsDictionaryValueDate -%} +{%- elsif IsDictionaryValueDate -%} ({{ Variable }})[key] = {{ Value }}[key] ? {% if UseJsDate %}formatDate({{ Value }}[key]){% else %}{{ Value }}[key].{{ DateToStringCode }}{% endif %} : {{ NullValue }}; -{% elseif IsDictionaryValueDateTime -%} +{%- elsif IsDictionaryValueDateTime -%} ({{ Variable }})[key] = {{ Value }}[key] ? {{ Value }}[key].{{ DateTimeToStringCode }} : {{ NullValue }}; -{% else -%} -{% if NullValue != "undefined" -%} +{%- else -%} +{%- if NullValue != "undefined" -%} ({{ Variable }})[key] = {{ Value }}[key] !== undefined ? {{ Value }}[key] : {{ NullValue }}; -{% else -%} +{%- else -%} ({{ Variable }})[key] = {{ Value }}[key]; -{% endif -%} -{% endif -%} +{%- endif -%} +{%- endif -%} } } -{% elseif IsDate -%} -{{ Variable }} = {{ Value }} ? {% if UseJsDate %}formatDate({{ Value }}){% else %}{{ Value }}.{{ DateToStringCode }}{% endif %} : {% if HasDefaultValue -%}{{ DefaultValue }}{% else %}{{ NullValue }}{% endif %}; -{% elseif IsDateTime -%} +{%- elsif IsDate -%} +{{ Variable }} = {{ Value }} ? {% if UseJsDate %}formatDate({{ Value }}){% else %}{{ Value }}.{{ DateToStringCode }}{% endif %} : {% if HasDefaultValue %}{{ DefaultValue }}{% else %}{{ NullValue }}{% endif %}; +{%- elsif IsDateTime -%} {{ Variable }} = {{ Value }} ? {{ Value }}.{{ DateTimeToStringCode }} : {% if HasDefaultValue %}{{ DefaultValue }}{% else %}{{ NullValue }}{% endif %}; -{% elseif NullValue != "undefined" -%} +{%- elsif NullValue != "undefined" -%} {{ Variable }} = {{ Value }} !== undefined ? {{ Value }} : {{ NullValue }}; -{% else -%} +{%- else -%} {{ Variable }} = {{ Value }}; -{% endif -%} \ No newline at end of file +{%- endif %} \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration.TypeScript/Templates/Enum.StringLiteral.liquid b/src/NJsonSchema.CodeGeneration.TypeScript/Templates/Enum.StringLiteral.liquid index 2e364e8eb..99864f7b6 100644 --- a/src/NJsonSchema.CodeGeneration.TypeScript/Templates/Enum.StringLiteral.liquid +++ b/src/NJsonSchema.CodeGeneration.TypeScript/Templates/Enum.StringLiteral.liquid @@ -1,4 +1,4 @@ -{% if HasDescription -%} +{%- if HasDescription -%} /** {{ Description }} */ -{% endif -%} -{% if ExportTypes %}export {% endif %}type {{ Name }} = {% for enumeration in Enums -%}{% if Enums.first.Value != enumeration.Value %} | {% endif %}{{ enumeration.Value }}{% endfor -%}; \ No newline at end of file +{%- endif -%} +{%- if ExportTypes %}export {% endif %}type {{ Name }} = {% for enumeration in Enums %}{%- if Enums.first.Value != enumeration.Value %} | {% endif %}{{ enumeration.Value }}{% endfor %}; \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration.TypeScript/Templates/Enum.liquid b/src/NJsonSchema.CodeGeneration.TypeScript/Templates/Enum.liquid index 6386839b4..14f1563a4 100644 --- a/src/NJsonSchema.CodeGeneration.TypeScript/Templates/Enum.liquid +++ b/src/NJsonSchema.CodeGeneration.TypeScript/Templates/Enum.liquid @@ -1,8 +1,8 @@ -{% if HasDescription -%} +{%- if HasDescription -%} /** {{ Description }} */ -{% endif -%} +{%- endif -%} {% if ExportTypes %}export {% endif %}enum {{ Name }} { -{% for enumeration in Enums -%} +{%- for enumeration in Enums -%} {{ enumeration.Name }} = {{ enumeration.Value }}, -{% endfor -%} +{%- endfor -%} } \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration.TypeScript/Templates/File.liquid b/src/NJsonSchema.CodeGeneration.TypeScript/Templates/File.liquid index c137551ff..e8487fd3c 100644 --- a/src/NJsonSchema.CodeGeneration.TypeScript/Templates/File.liquid +++ b/src/NJsonSchema.CodeGeneration.TypeScript/Templates/File.liquid @@ -6,21 +6,21 @@ {{ ExtensionCode.ImportCode }} -{% if HasModuleName -%} +{%- if HasModuleName %} module {{ ModuleName }} { -{% endif -%} -{% if HasNamespace -%} +{%- endif %} +{%- if HasNamespace %} namespace {{ Namespace }} { -{% endif -%} +{%- endif %} {{ ExtensionCode.TopCode }} {{ Types }} {{ ExtensionCode.BottomCode }} -{% if HasNamespace -%} +{%- if HasNamespace %} } -{% endif -%} -{% if HasModuleName -%} +{%- endif %} +{%- if HasModuleName %} } -{% endif -%} \ No newline at end of file +{%- endif %} \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration.TypeScript/Templates/Interface.liquid b/src/NJsonSchema.CodeGeneration.TypeScript/Templates/Interface.liquid index 5ab7583bf..1396d01ed 100644 --- a/src/NJsonSchema.CodeGeneration.TypeScript/Templates/Interface.liquid +++ b/src/NJsonSchema.CodeGeneration.TypeScript/Templates/Interface.liquid @@ -1,15 +1,15 @@ -{% if HasDescription -%} +{%- if HasDescription -%} /** {{ Description }} */ -{% endif -%} +{%- endif -%} {% if ExportTypes %}export {% endif %}interface {{ ClassName }}{{ Inheritance }} { -{% for property in Properties -%} -{% if property.HasDescription -%} +{%- for property in Properties -%} +{%- if property.HasDescription -%} /** {{ property.Description }} */ -{% endif -%} +{%- endif -%} {% if property.IsReadOnly %}readonly {% endif %}{{ property.InterfaceName }}{% if property.IsOptional %}?{% endif %}: {{ property.Type }}{{ property.TypePostfix }}; -{% endfor -%} -{% if HasIndexerProperty -%} +{%- endfor -%} +{%- if HasIndexerProperty -%} - [key: string]: {{ IndexerPropertyValueType }}; -{% endif -%} + [key: string]: {{ IndexerPropertyValueType }}; +{%- endif -%} } \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration.TypeScript/Templates/KnockoutClass.liquid b/src/NJsonSchema.CodeGeneration.TypeScript/Templates/KnockoutClass.liquid index 8a108c6b0..5ab33efd5 100644 --- a/src/NJsonSchema.CodeGeneration.TypeScript/Templates/KnockoutClass.liquid +++ b/src/NJsonSchema.CodeGeneration.TypeScript/Templates/KnockoutClass.liquid @@ -1,99 +1,99 @@ -{% if HasDescription -%} +{%- if HasDescription -%} /** {{ Description }} */ -{% endif -%} +{%- endif -%} {% if ExportTypes %}export {% endif %}class {{ ClassName }}{{ Inheritance }} { -{% for property in Properties -%} -{% if property.HasDescription -%} +{%- for property in Properties -%} +{%- if property.HasDescription -%} /** {{ property.Description }} */ -{% endif -%} +{%- endif -%} {% if property.IsReadOnly %}readonly {% endif %}{{ property.PropertyName }} = {% if property.IsArray %}ko.observableArray<{{ property.ArrayItemType }}>({% if property.HasDefaultValue %}{{ property.DefaultValue }}{% endif %}){% else %}ko.observable<{{ property.Type }}{{ property.TypePostfix }}>({% if property.HasDefaultValue %}{{ property.DefaultValue }}{% endif %}){% endif %}; -{% endfor -%} +{%- endfor -%} -{% if HasDiscriminator -%} +{%- if HasDiscriminator -%} protected _discriminator: string; -{% endif -%} +{%- endif -%} -{% if HasBaseDiscriminator -%} +{%- if HasBaseDiscriminator -%} constructor() { -{% if HasInheritance -%} +{%- if HasInheritance -%} super(); -{% endif -%} +{%- endif -%} this._discriminator = "{{ DiscriminatorName }}"; } -{% endif -%} +{%- endif -%} init(data?: any{% if HandleReferences %}, _mappings?: any{% endif %}) { -{% if HasInheritance -%} +{%- if HasInheritance -%} super.init(data); -{% endif -%} +{%- endif -%} if (data !== undefined) { -{% for property in Properties -%} - var {{ property.PropertyName }}_: any; +{%- for property in Properties -%} + var {{ property.PropertyName }}_: any; {{ property.ConvertToClassCode | tab }} this.{{ property.PropertyName }}({{ property.PropertyName }}_); -{% endfor -%} +{%- endfor -%} } } static fromJS(data: any{% if HandleReferences %}, _mappings?: any{% endif %}): {{ ClassName }}{% if HandleReferences %} | null{% endif %} { -{% if HandleReferences -%} -{% if HasBaseDiscriminator -%} -{% for derivedClass in DerivedClasses -%} +{%- if HandleReferences -%} +{%- if HasBaseDiscriminator -%} +{%- for derivedClass in DerivedClasses -%} if (data["{{ BaseDiscriminator }}"] === "{{ derivedClass.Discriminator }}") -{% if derivedClass.IsAbstract -%} +{%- if derivedClass.IsAbstract -%} throw new Error("The abstract class '{{ derivedClass.ClassName }}' cannot be instantiated."); -{% else -%} +{%- else -%} return createInstance<{{ derivedClass.ClassName }}>(data, _mappings, {{ derivedClass.ClassName }}); -{% endif -%} -{% endfor -%} -{% endif -%} -{% if IsAbstract -%} +{%- endif -%} +{%- endfor -%} +{%- endif -%} +{%- if IsAbstract -%} throw new Error("The abstract class '{{ ClassName }}' cannot be instantiated."); -{% else -%} +{%- else -%} return createInstance<{{ ClassName }}>(data, _mappings, {{ ClassName }}); -{% endif -%} -{% else -%} -{% if HasBaseDiscriminator -%} -{% for derivedClass in DerivedClasses -%} +{%- endif -%} +{%- else -%} +{%- if HasBaseDiscriminator -%} +{%- for derivedClass in DerivedClasses -%} if (data["{{ BaseDiscriminator }}"] === "{{ derivedClass.Discriminator }}") { -{% if derivedClass.IsAbstract -%} +{%- if derivedClass.IsAbstract -%} throw new Error("The abstract class '{{ derivedClass.ClassName }}' cannot be instantiated."); -{% else -%} +{%- else -%} let result = new {{ derivedClass.ClassName }}(); result.init(data); return result; -{% endif -%} +{%- endif -%} } -{% endfor -%} -{% endif -%} -{% if IsAbstract -%} +{%- endfor -%} +{%- endif -%} +{%- if IsAbstract -%} throw new Error("The abstract class '{{ ClassName }}' cannot be instantiated."); -{% else -%} +{%- else -%} let result = new {{ ClassName }}(); result.init(data); return result; -{% endif -%} -{% endif -%} +{%- endif -%} +{%- endif -%} } toJSON(data?: any) { data = typeof data === 'object' ? data : {}; -{% if HasDiscriminator -%} - data["{{ BaseDiscriminator }}"] = this._discriminator; -{% endif -%} -{% for property in Properties -%} - let {{ property.PropertyName }}_: any = this.{{ property.PropertyName }}(); +{%- if HasDiscriminator -%} + data["{{ BaseDiscriminator }}"] = this._discriminator; +{%- endif -%} +{%- for property in Properties -%} + let {{ property.PropertyName }}_: any = this.{{ property.PropertyName }}(); {{ property.ConvertToJavaScriptCode | tab }} -{% endfor -%} -{% if HasInheritance -%} +{%- endfor -%} +{%- if HasInheritance -%} super.toJSON(data); -{% endif -%} - return data; +{%- endif -%} + return data; } -{% if GenerateCloneMethod -%} +{%- if GenerateCloneMethod -%} clone() { const json = this.toJSON(); @@ -101,5 +101,5 @@ result.init(json); return result; } -{% endif -%} +{%- endif -%} } \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration/CodeArtifactExtensions.cs b/src/NJsonSchema.CodeGeneration/CodeArtifactExtensions.cs index 03ef578ea..63f3f3d1d 100644 --- a/src/NJsonSchema.CodeGeneration/CodeArtifactExtensions.cs +++ b/src/NJsonSchema.CodeGeneration/CodeArtifactExtensions.cs @@ -28,7 +28,10 @@ public static string Concatenate(this IEnumerable artifacts) public static IEnumerable OrderByBaseDependency(this IEnumerable results) { var newResults = new List(results); - foreach (var result in newResults.ToArray()) + + // we need new list to iterate as we modify the original + var resultIterator = results as List ?? newResults.ToList(); + foreach (var result in resultIterator) { if (!string.IsNullOrEmpty(GetActualBaseName(result.BaseTypeName))) { @@ -36,14 +39,20 @@ public static IEnumerable OrderByBaseDependency(this IEnumerable r.TypeName == GetActualBaseName(baseResult.BaseTypeName)); - if (baseResult != null) + var actualBaseName = GetActualBaseName(baseResult.BaseTypeName); + baseResult = null; + for (var baseIndex = 0; baseIndex < newResults.Count; baseIndex++) { - var baseIndex = newResults.IndexOf(baseResult); - if (baseIndex > index) + var candidate = newResults[baseIndex]; + if (candidate.TypeName == actualBaseName) { - newResults.RemoveAt(baseIndex); - newResults.Insert(index, baseResult); + baseResult = candidate; + if (baseIndex > index) + { + newResults.RemoveAt(baseIndex); + newResults.Insert(index, candidate); + } + break; } } } while (baseResult != null); @@ -66,7 +75,12 @@ private static string GetActualBaseName(string baseTypeName) } // resolve lists - return Regex.Replace(baseTypeName, ".*\\<(.*)\\>", m => m.Groups[1].Value); + if (baseTypeName.IndexOf('<') > -1) + { + return Regex.Replace(baseTypeName, ".*\\<(.*)\\>", m => m.Groups[1].Value); + } + + return baseTypeName; } } } \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration/DefaultTemplateFactory.cs b/src/NJsonSchema.CodeGeneration/DefaultTemplateFactory.cs index 62f47e186..f3e684704 100644 --- a/src/NJsonSchema.CodeGeneration/DefaultTemplateFactory.cs +++ b/src/NJsonSchema.CodeGeneration/DefaultTemplateFactory.cs @@ -6,7 +6,6 @@ // Rico Suter, mail@rsuter.com //----------------------------------------------------------------------- -using DotLiquid; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -14,7 +13,13 @@ using System.IO; using System.Linq; using System.Reflection; +using System.Text.Encodings.Web; using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Fluid; +using Fluid.Ast; +using Fluid.Values; +using Parlot.Fluent; namespace NJsonSchema.CodeGeneration { @@ -41,8 +46,13 @@ public DefaultTemplateFactory(CodeGeneratorSettingsBase settings, Assembly[] ass /// Could not load template. public ITemplate CreateTemplate(string language, string template, object model) { - var liquidTemplate = GetLiquidTemplate(language, template); - return new LiquidTemplate(language, template, liquidTemplate, model, GetToolchainVersion(), _settings); + return new LiquidTemplate( + language, + template, + (lang, name) => GetLiquidTemplate(lang, name), + model, + GetToolchainVersion(), + _settings); } /// Gets the current toolchain version. @@ -105,28 +115,49 @@ private string GetLiquidTemplate(string language, string template) return GetEmbeddedLiquidTemplate(language, template); } - internal class LiquidTemplate : ITemplate + private sealed class LiquidTemplate : ITemplate { - private const string TemplateTagName = "__njs_template"; - private static readonly ConcurrentDictionary Templates = new ConcurrentDictionary(); + internal const string TemplateTagName = "template"; + private static readonly ConcurrentDictionary<(string, string), IFluidTemplate> Templates = new ConcurrentDictionary<(string, string), IFluidTemplate>(); static LiquidTemplate() { - Template.RegisterTag(TemplateTagName); + // thread-safe + _parser = new LiquidParser(); + _templateOptions = new TemplateOptions + { + MemberAccessStrategy = new UnsafeMemberAccessStrategy(), + CultureInfo = CultureInfo.InvariantCulture, + Greedy = false + }; + _templateOptions.Filters.AddFilter("csharpdocs", LiquidFilters.Csharpdocs); + _templateOptions.Filters.AddFilter("tab", LiquidFilters.Tab); + _templateOptions.Filters.AddFilter("lowercamelcase", LiquidFilters.Lowercamelcase); + _templateOptions.Filters.AddFilter("uppercamelcase", LiquidFilters.Uppercamelcase); + _templateOptions.Filters.AddFilter("literal", LiquidFilters.Literal); } private readonly string _language; private readonly string _template; - private readonly string _data; + private readonly Func _templateContentLoader; private readonly object _model; private readonly string _toolchainVersion; private readonly CodeGeneratorSettingsBase _settings; - public LiquidTemplate(string language, string template, string data, object model, string toolchainVersion, CodeGeneratorSettingsBase settings) + private static readonly LiquidParser _parser; + private static readonly TemplateOptions _templateOptions; + + public LiquidTemplate( + string language, + string template, + Func templateContentLoader, + object model, + string toolchainVersion, + CodeGeneratorSettingsBase settings) { _language = language; _template = template; - _data = data; + _templateContentLoader = templateContentLoader; _model = model; _toolchainVersion = toolchainVersion; _settings = settings; @@ -134,25 +165,40 @@ public LiquidTemplate(string language, string template, string data, object mode public string Render() { + var childScope = false; + TemplateContext templateContext = null; try { - var hash = _model is Hash ? (Hash)_model : new LiquidProxyHash(_model); - hash[TemplateTag.LanguageKey] = _language; - hash[TemplateTag.TemplateKey] = _template; - hash[TemplateTag.SettingsKey] = _settings; - hash["ToolchainVersion"] = _toolchainVersion; - - if (!Templates.ContainsKey(_data)) + // use language and template name as key for faster lookup than using the content + var key = (_language, _template); + var template = Templates.GetOrAdd(key, _ => { - var data = Regex.Replace("\n" + _data, "(\n( )*?)\\{% template (.*?) %}", m => - "\n{%- " + TemplateTagName + " " + m.Groups[3].Value + " " + m.Groups[1].Value.Length / 4 + " -%}", - RegexOptions.Singleline).Trim(); - - data = Regex.Replace("\n" + data, "\\{% template (.*?) %}", m => - "{% " + TemplateTagName + " " + m.Groups[1].Value + " -1 %}", - RegexOptions.Singleline).Trim(); - - data = data.Replace("{% template %}", "{% " + TemplateTagName + " %}"); + // our matching expects unix new lines + var data = _templateContentLoader(_language, _template).Replace("\r", ""); + data = "\n" + data; + + // tab count parameters to template based on surrounding code, how many spaces before the template tag + data = Regex.Replace(data, "(\\s*)?\\{%(-)?\\s+template\\s+([a-zA-Z0-9_.]+)(\\s*?.*?)\\s(-)?%}", + m => + { + var whitespace = m.Groups[1].Value; + + var rewritten = whitespace + "{%" + m.Groups[2].Value + " " + TemplateTagName; + // make te parameter a string literal as it's more valid and faster to process + rewritten += " '" + m.Groups[3].Value + "' "; + + if (whitespace.Length > 0 && whitespace[0] == '\n') + { + // we can checks how many spaces + var tabCount = whitespace.TrimStart('\n').Length / 4; + rewritten += tabCount + " "; + } + + rewritten += m.Groups[5].Value + "%}"; + + return rewritten; + }, + RegexOptions.Singleline); data = Regex.Replace(data, "(\n( )*)([^\n]*?) \\| csharpdocs }}", m => m.Groups[1].Value + m.Groups[3].Value + " | csharpdocs: " + m.Groups[1].Value.Length / 4 + " }}", @@ -162,127 +208,157 @@ public string Render() m.Groups[1].Value + m.Groups[3].Value + " | tab: " + m.Groups[1].Value.Length / 4 + " }}", RegexOptions.Singleline); - Templates[_data] = data; // TODO: How to cache thread-safe parsed template? - } + return _parser.Parse(data); + }); - var template = Template.Parse(Templates[_data]); - return template.Render(new RenderParameters(CultureInfo.InvariantCulture) + if (_model is TemplateContext outerContext) + { + // we came from template call + templateContext = outerContext; + templateContext.EnterChildScope(); + childScope = true; + } + else { - LocalVariables = hash, - Filters = new[] { typeof(LiquidFilters) }, - ErrorsOutputMode = ErrorsOutputMode.Rethrow - }).Replace("\r", "").Trim(); + templateContext = new TemplateContext(_model, _templateOptions); + templateContext.AmbientValues.Add(LiquidParser.SettingsKey, _settings); + templateContext.AmbientValues.Add("ToolchainVersion", _toolchainVersion); + } + + templateContext.AmbientValues[LiquidParser.LanguageKey] = _language; + templateContext.AmbientValues[LiquidParser.TemplateKey] = _template; + + var render = template.Render(templateContext); + var trimmed = render.Replace("\r", "").Trim('\n'); + + // clean up cases where we have called template but it produces empty output + var withoutEmptyWhiteSpace = Regex.Replace(trimmed, @"^[ ]+__EMPTY-TEMPLATE__$[\n]{0,1}", string.Empty, RegexOptions.Multiline); + + // just to make sure we don't leak out marker + return withoutEmptyWhiteSpace.Replace("__EMPTY-TEMPLATE__", ""); } catch (Exception exception) { - throw new InvalidOperationException($"Error while rendering Liquid template {_language}/{_template}: \n" + exception, exception); + throw new InvalidOperationException( + $"Error while rendering Liquid template {_language}/{_template}: \n" + exception, exception); + } + finally + { + if (childScope) + { + templateContext.ReleaseScope(); + } } } } - internal static class LiquidFilters + private static class LiquidFilters { - public static string Csharpdocs(string input, int tabCount) - { - return ConversionUtilities.ConvertCSharpDocs(input, tabCount); - } - - public static string Tab(Context context, string input, int tabCount) - { - return ConversionUtilities.Tab(input, tabCount); - } - - public static string Lowercamelcase(Context context, string input, bool firstCharacterMustBeAlpha = true) + public static ValueTask Csharpdocs(FluidValue input, FilterArguments arguments, TemplateContext context) { - return ConversionUtilities.ConvertToLowerCamelCase(input, firstCharacterMustBeAlpha); + var tabCount = (int) arguments.At(0).ToNumberValue(); + var converted = ConversionUtilities.ConvertCSharpDocs(input.ToStringValue(), tabCount); + return new ValueTask(new StringValue(converted)); } - public static string Uppercamelcase(Context context, string input, bool firstCharacterMustBeAlpha = true) + public static ValueTask Tab(FluidValue input, FilterArguments arguments, TemplateContext context) { - return ConversionUtilities.ConvertToUpperCamelCase(input, firstCharacterMustBeAlpha); + var tabCount = (int) arguments.At(0).ToNumberValue(); + var converted = ConversionUtilities.Tab(input.ToStringValue(), tabCount); + return new ValueTask(new StringValue(converted)); } - public static string Literal(string input) + public static ValueTask Lowercamelcase(FluidValue input, FilterArguments arguments, TemplateContext context) { - return "\"" + ConversionUtilities.ConvertToStringLiteral(input) + "\""; + var firstCharacterMustBeAlpha = arguments["firstCharacterMustBeAlpha"].ToBooleanValue(); + var converted = ConversionUtilities.ConvertToLowerCamelCase(input.ToStringValue(), firstCharacterMustBeAlpha); + return new ValueTask(new StringValue(converted)); } - public static IEnumerable Concat(Context context, IEnumerable input, IEnumerable concat) + public static ValueTask Uppercamelcase(FluidValue input, FilterArguments arguments, TemplateContext context) { - return input.Concat(concat ?? Enumerable.Empty()).ToList(); + var firstCharacterMustBeAlpha = arguments["firstCharacterMustBeAlpha"].ToBooleanValue(); + var converted = ConversionUtilities.ConvertToUpperCamelCase(input.ToStringValue(), firstCharacterMustBeAlpha); + return new ValueTask(new StringValue(converted)); } - public static IEnumerable Empty(Context context, object input) + public static ValueTask Literal(FluidValue input, FilterArguments arguments, TemplateContext context) { - return Enumerable.Empty(); + var converted = "\"" + ConversionUtilities.ConvertToStringLiteral(input.ToStringValue()) + "\""; + return new ValueTask(new StringValue(converted, encode: false)); } } - internal class TemplateTag : Tag + private sealed class LiquidParser : FluidParser { - public static string LanguageKey = "__language"; - public static string TemplateKey = "__template"; - public static string SettingsKey = "__settings"; - - private string _template; - private int _tabCount; + internal const string LanguageKey = "__language"; + internal const string TemplateKey = "__template"; + internal const string SettingsKey = "__settings"; - public override void Initialize(string tagName, string markup, List tokens) + public LiquidParser() { - var parts = markup.Trim().Split(' ').Where(p => !string.IsNullOrWhiteSpace(p)).ToArray(); - _template = parts.Length >= 1 ? parts[0] : null; - _tabCount = parts.Length >= 2 ? int.Parse(parts[1]) : 0; - base.Initialize(tagName, markup, tokens); + RegisterParserTag(LiquidTemplate.TemplateTagName, Parsers.OneOrMany(Primary), RenderTemplate); } - public override void Render(Context context, TextWriter result) + private static ValueTask RenderTemplate( + List arguments, + TextWriter writer, + TextEncoder encoder, + TemplateContext context) { - try + var templateName = ((LiteralExpression) arguments[0]).Value.ToStringValue(); + + var tabCount = -1; + if (arguments.Count > 1 && arguments[1] is LiteralExpression literalExpression) { - var model = CreateModelWithParentContext(context); + tabCount = (int) literalExpression.Value.ToNumberValue(); + } - var rootContext = context.Environments[0]; - var settings = (CodeGeneratorSettingsBase)rootContext[SettingsKey]; - var language = (string)rootContext[LanguageKey]; - var templateName = !string.IsNullOrEmpty(_template) ? _template : (string)rootContext[TemplateKey] + "!"; + var settings = (CodeGeneratorSettingsBase) context.AmbientValues[SettingsKey]; + var language = (string) context.AmbientValues[LanguageKey]; + templateName = !string.IsNullOrEmpty(templateName) + ? templateName + : (string) context.AmbientValues[TemplateKey] + "!"; - var template = settings.TemplateFactory.CreateTemplate(language, templateName, model); - var output = template.Render().Trim(); + var template = settings.TemplateFactory.CreateTemplate(language, templateName, context); + var output = template.Render(); - if (string.IsNullOrEmpty(output)) - { - result.Write(""); - } - else if (_tabCount >= 0) - { - result.Write(string.Join("", Enumerable.Repeat(" ", _tabCount)) + - ConversionUtilities.Tab(output, _tabCount) + "\r\n"); - } - else - { - result.Write(output); - } + if (string.IsNullOrWhiteSpace(output)) + { + // signal cleanup + writer.Write("__EMPTY-TEMPLATE__"); } - catch (InvalidOperationException) + else if (tabCount > 0) { + ConversionUtilities.Tab(output, tabCount, writer); } - } - - private LiquidProxyHash CreateModelWithParentContext(Context context) - { - var model = new LiquidProxyHash(((LiquidProxyHash)context.Environments[0]).Object); - model.Merge(context.Registers); - foreach (var scope in Enumerable.Reverse(context.Scopes)) + else { - model.Merge(scope); + writer.Write(output); } - foreach (var environment in Enumerable.Reverse(context.Environments)) + return new ValueTask(Completion.Normal); + } + } + + /// + /// Version that allows all access, safe as models are handled by NJsonSchema. + /// + private sealed class UnsafeMemberAccessStrategy : DefaultMemberAccessStrategy + { + private readonly MemberAccessStrategy baseMemberAccessStrategy = new DefaultMemberAccessStrategy(); + + public override IMemberAccessor GetAccessor(Type type, string name) + { + var accessor = baseMemberAccessStrategy.GetAccessor(type, name); + if (accessor != null) { - model.Merge(environment); + return accessor; } - return model; + baseMemberAccessStrategy.Register(type); + accessor = baseMemberAccessStrategy.GetAccessor(type, name); + return accessor; } } } diff --git a/src/NJsonSchema.CodeGeneration/LiquidProxyHash.cs b/src/NJsonSchema.CodeGeneration/LiquidProxyHash.cs deleted file mode 100644 index f3ad19cef..000000000 --- a/src/NJsonSchema.CodeGeneration/LiquidProxyHash.cs +++ /dev/null @@ -1,134 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Rico Suter. All rights reserved. -// -// https://github.com/RicoSuter/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; -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("NJsonSchema.CodeGeneration.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100337d8a0b73ac39048dc55d8e48dd86dcebd0af16aa514c73fbf5f283a8e94d7075b4152e5621e18d234bf7a5aafcb6683091f79d87b80c3be3e806f688e6f940adf92b28cedf1f8f69aa443699c235fa049204b56b83d94f599dd9800171f28e45ab74351acab17d889cd65961354d2f6405bddb9e896956e69e60033c2574f1")] - -namespace NJsonSchema.CodeGeneration -{ - internal class LiquidProxyHash : Hash - { - private readonly IDictionary _properties; - - public LiquidProxyHash(object obj) - { - Object = obj; - _properties = obj?.GetType().GetRuntimeProperties() - .ToDictionary(p => p.Name, p => p) ?? - new Dictionary(); - } - - public object Object { get; } - - public override bool Contains(object key) - { - return _properties.ContainsKey(key.ToString()) || base.Contains(key); - } - - protected override object GetValue(string key) - { - if (_properties.ContainsKey(key)) - { - var value = _properties[key].GetValue(Object); - if (IsObject(value)) - { - return GetObjectValue(value); - } - - return value; - } - else - { - return base.GetValue(key); - } - } - - private object GetObjectValue(object value) - { - if (value is IDictionary dictionary) - { - return CreateDictionaryHash(dictionary); - } - else if (value is IEnumerable enumerable) - { - if (enumerable.OfType().All(i => !IsObject(i))) - { - var list = new List(); - foreach (var item in enumerable) - { - list.Add(item); - } - - return list; - } - else if (enumerable.OfType().All(i => i is IDictionary)) - { - var list = new List(); - foreach (IDictionary item in enumerable) - { - list.Add(CreateDictionaryHash(item)); - } - - return list; - } - else - { - var list = new List(); - foreach (var item in enumerable) - { - list.Add(new LiquidProxyHash(item)); - } - - return list; - } - } - else - { - return new LiquidProxyHash(value); - } - } - - private object CreateDictionaryHash(IDictionary dictionary) - { - var hash = new Hash(); - foreach (var k in dictionary.Keys) - { - var v = dictionary[k]; - hash[k.ToString()] = IsObject(v) ? GetObjectValue(v) : v; - } - - return hash; - } - - public override int GetHashCode() - { - return base.GetHashCode(); - } - - public override bool Equals(object obj) - { - if (obj is LiquidProxyHash hash && hash.Object != Object) - { - return false; - } - - return base.Equals(obj); - } - - private static bool IsObject(object value) - { - return value != null && value.GetType().GetTypeInfo().IsClass && !(value is string); - } - } -} \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration/NJsonSchema.CodeGeneration.csproj b/src/NJsonSchema.CodeGeneration/NJsonSchema.CodeGeneration.csproj index 848c34b3a..7934110fb 100644 --- a/src/NJsonSchema.CodeGeneration/NJsonSchema.CodeGeneration.csproj +++ b/src/NJsonSchema.CodeGeneration/NJsonSchema.CodeGeneration.csproj @@ -1,6 +1,6 @@  - netstandard1.3;netstandard2.0;net451 + netstandard2.0;net461 JSON Schema reader, generator and validator for .NET 10.5.2 json schema validation generator .net @@ -17,18 +17,10 @@ bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml - - true - - - + - - - - 4.3.0 - + diff --git a/src/NJsonSchema.CodeGeneration/TypeResolverBase.cs b/src/NJsonSchema.CodeGeneration/TypeResolverBase.cs index afcb54e06..e74328979 100644 --- a/src/NJsonSchema.CodeGeneration/TypeResolverBase.cs +++ b/src/NJsonSchema.CodeGeneration/TypeResolverBase.cs @@ -53,13 +53,14 @@ public virtual string GetOrGenerateTypeName(JsonSchema schema, string typeNameHi RegisterSchemaDefinitions(schema.Definitions); - if (!_generatedTypeNames.ContainsKey(schema)) + if (!_generatedTypeNames.TryGetValue(schema, out var typeNames)) { - var reservedTypeNames = _generatedTypeNames.Values.Distinct().ToList(); - _generatedTypeNames[schema] = _settings.TypeNameGenerator.Generate(schema, typeNameHint, reservedTypeNames); + var reservedTypeNames = new HashSet(_generatedTypeNames.Values); + typeNames = _settings.TypeNameGenerator.Generate(schema, typeNameHint, reservedTypeNames); + _generatedTypeNames[schema] = typeNames; } - return _generatedTypeNames[schema]; + return typeNames; } /// Adds all schemas to the resolver. @@ -89,7 +90,7 @@ public JsonSchema RemoveNullability(JsonSchema schema) return schema.OneOf.FirstOrDefault(o => !o.IsNullable(SchemaType.JsonSchema)) ?? schema; } - /// Gets the actual schema (i.e. when not referencing a type schema or it is inlined) + /// Gets the actual schema (i.e. when not referencing a type schema or it is inlined) /// and removes a nullable oneOf reference if available. /// The schema. /// The actually resolvable schema @@ -99,7 +100,7 @@ public JsonSchema GetResolvableSchema(JsonSchema schema) return IsDefinitionTypeSchema(schema.ActualSchema) ? schema : schema.ActualSchema; } - /// Checks whether the given schema generates a new type (e.g. class, enum, class with dictionary inheritance, etc.) + /// Checks whether the given schema generates a new type (e.g. class, enum, class with dictionary inheritance, etc.) /// or is an inline type (e.g. string, number, etc.). Warning: Enum will also return true. /// /// diff --git a/src/NJsonSchema.Demo.Performance/NJsonSchema.Demo.Performance.csproj b/src/NJsonSchema.Demo.Performance/NJsonSchema.Demo.Performance.csproj index becc9f21f..6a085f69f 100644 --- a/src/NJsonSchema.Demo.Performance/NJsonSchema.Demo.Performance.csproj +++ b/src/NJsonSchema.Demo.Performance/NJsonSchema.Demo.Performance.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp5.0 + net5.0 diff --git a/src/NJsonSchema.Demo/NJsonSchema.Demo.csproj b/src/NJsonSchema.Demo/NJsonSchema.Demo.csproj index 277de7636..6d0a71f9f 100644 --- a/src/NJsonSchema.Demo/NJsonSchema.Demo.csproj +++ b/src/NJsonSchema.Demo/NJsonSchema.Demo.csproj @@ -1,6 +1,6 @@  - netcoreapp5.0 + net5.0 false true @@ -12,101 +12,6 @@ - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - - + diff --git a/src/NJsonSchema.Tests/Conversion/TypeToSchemaTests.cs b/src/NJsonSchema.Tests/Conversion/TypeToSchemaTests.cs index 1ee711967..4988eb4ed 100644 --- a/src/NJsonSchema.Tests/Conversion/TypeToSchemaTests.cs +++ b/src/NJsonSchema.Tests/Conversion/TypeToSchemaTests.cs @@ -19,7 +19,7 @@ public async Task When_converting_in_round_trip_then_json_should_be_the_same() //// Arrange var schema = JsonSchema.FromType(); - //// Act + /// Act var schemaData1 = JsonConvert.SerializeObject(schema, Formatting.Indented); var schema2 = JsonConvert.DeserializeObject(schemaData1); var schemaData2 = JsonConvert.SerializeObject(schema2, Formatting.Indented); diff --git a/src/NJsonSchema.Tests/Generation/ContractResolverTests.cs b/src/NJsonSchema.Tests/Generation/ContractResolverTests.cs index 4c534cb5b..042d90797 100644 --- a/src/NJsonSchema.Tests/Generation/ContractResolverTests.cs +++ b/src/NJsonSchema.Tests/Generation/ContractResolverTests.cs @@ -13,9 +13,7 @@ namespace NJsonSchema.Tests.Generation { public class ContractResolverTests { -#if !NET452 [Fact] -#endif public async Task Properties_should_match_custom_resolver() { var schema = JsonSchema.FromType(new JsonSchemaGeneratorSettings diff --git a/src/NJsonSchema.Tests/Generation/SystemTextJson/JsonInheritanceAttribute.cs b/src/NJsonSchema.Tests/Generation/SystemTextJson/JsonInheritanceAttribute.cs index fa858e0fb..95182aba5 100644 --- a/src/NJsonSchema.Tests/Generation/SystemTextJson/JsonInheritanceAttribute.cs +++ b/src/NJsonSchema.Tests/Generation/SystemTextJson/JsonInheritanceAttribute.cs @@ -1,4 +1,4 @@ -#if !NET46 && !NET452 +#if !NET461 using System; diff --git a/src/NJsonSchema.Tests/Generation/SystemTextJson/JsonInheritanceConverter.cs b/src/NJsonSchema.Tests/Generation/SystemTextJson/JsonInheritanceConverter.cs index a495d75a7..83cdcbbc5 100644 --- a/src/NJsonSchema.Tests/Generation/SystemTextJson/JsonInheritanceConverter.cs +++ b/src/NJsonSchema.Tests/Generation/SystemTextJson/JsonInheritanceConverter.cs @@ -1,4 +1,4 @@ -#if !NET46 && !NET452 +#if !NET461 using System; using System.Collections.Generic; diff --git a/src/NJsonSchema.Tests/Generation/SystemTextJson/JsonInheritanceConverterAttribute.cs b/src/NJsonSchema.Tests/Generation/SystemTextJson/JsonInheritanceConverterAttribute.cs index 37a49eb19..0accb75be 100644 --- a/src/NJsonSchema.Tests/Generation/SystemTextJson/JsonInheritanceConverterAttribute.cs +++ b/src/NJsonSchema.Tests/Generation/SystemTextJson/JsonInheritanceConverterAttribute.cs @@ -1,4 +1,4 @@ -#if !NET46 && !NET452 +#if !NET461 using System; using System.Text.Json.Serialization; diff --git a/src/NJsonSchema.Tests/Generation/SystemTextJson/SystemTextJsonExtensionDataGenerationTests.cs b/src/NJsonSchema.Tests/Generation/SystemTextJson/SystemTextJsonExtensionDataGenerationTests.cs index a22a2ce3b..649f359d0 100644 --- a/src/NJsonSchema.Tests/Generation/SystemTextJson/SystemTextJsonExtensionDataGenerationTests.cs +++ b/src/NJsonSchema.Tests/Generation/SystemTextJson/SystemTextJsonExtensionDataGenerationTests.cs @@ -1,4 +1,4 @@ -#if !NET46 && !NET452 +#if !NET461 using System.Collections.Generic; using System.Text.Json; diff --git a/src/NJsonSchema.Tests/Generation/SystemTextJson/SystemTextJsonInheritanceTests.cs b/src/NJsonSchema.Tests/Generation/SystemTextJson/SystemTextJsonInheritanceTests.cs index 88636d5d7..d5f8d319b 100644 --- a/src/NJsonSchema.Tests/Generation/SystemTextJson/SystemTextJsonInheritanceTests.cs +++ b/src/NJsonSchema.Tests/Generation/SystemTextJson/SystemTextJsonInheritanceTests.cs @@ -1,4 +1,4 @@ -#if !NET46 && !NET452 +#if !NET461 using System.Threading.Tasks; using Xunit; diff --git a/src/NJsonSchema.Tests/Generation/SystemTextJson/SystemTextJsonOptionsConverterTests.cs b/src/NJsonSchema.Tests/Generation/SystemTextJson/SystemTextJsonOptionsConverterTests.cs index 0fbbacceb..8c868583b 100644 --- a/src/NJsonSchema.Tests/Generation/SystemTextJson/SystemTextJsonOptionsConverterTests.cs +++ b/src/NJsonSchema.Tests/Generation/SystemTextJson/SystemTextJsonOptionsConverterTests.cs @@ -1,4 +1,4 @@ -#if !NET46 && !NET452 +#if !NET461 using Newtonsoft.Json.Converters; using NJsonSchema.Generation; diff --git a/src/NJsonSchema.Tests/NJsonSchema.Tests.csproj b/src/NJsonSchema.Tests/NJsonSchema.Tests.csproj index c3425d217..e7445acec 100644 --- a/src/NJsonSchema.Tests/NJsonSchema.Tests.csproj +++ b/src/NJsonSchema.Tests/NJsonSchema.Tests.csproj @@ -1,68 +1,27 @@  - netcoreapp5.0;net46;net452 - + net5.0;net461 false - 8 + latest bin\Debug\$(TargetFramework)\NJsonSchema.Tests.xml - 1998,1591 + $(NoWarn),618,1587,1998,1591 - - - - - - - - + - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - - - - - - - - - - - - - + + + + + diff --git a/src/NJsonSchema.Tests/Validation/NumberTests.cs b/src/NJsonSchema.Tests/Validation/NumberTests.cs index 7631a1d43..b49d1a388 100644 --- a/src/NJsonSchema.Tests/Validation/NumberTests.cs +++ b/src/NJsonSchema.Tests/Validation/NumberTests.cs @@ -38,7 +38,7 @@ public async Task When_double_is_bigger_then_decimal_then_validation_works() Assert.Equal(0, errors.Count); } - // [Fact] + [Fact(Skip = "Ignored")] public async Task When_integer_is_big_integer_then_validation_works() { // See https://github.com/RicoSuter/NJsonSchema/issues/568 diff --git a/src/NJsonSchema.Yaml.Tests/NJsonSchema.Yaml.Tests.csproj b/src/NJsonSchema.Yaml.Tests/NJsonSchema.Yaml.Tests.csproj index e55f782a8..a84eddb4d 100644 --- a/src/NJsonSchema.Yaml.Tests/NJsonSchema.Yaml.Tests.csproj +++ b/src/NJsonSchema.Yaml.Tests/NJsonSchema.Yaml.Tests.csproj @@ -1,22 +1,17 @@  - netcoreapp5.0;net46;net452 + netcoreapp5.0;net461 false bin\Debug\$(TargetFramework)\NJsonSchema.Yaml.Tests.xml - + - + - - + + - - - - - @@ -24,58 +19,7 @@ - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - - + + \ No newline at end of file diff --git a/src/NJsonSchema/Annotations/IJsonSchemaExtensionDataAttribute.cs b/src/NJsonSchema/Annotations/IJsonSchemaExtensionDataAttribute.cs index 642969b0d..436a4da29 100644 --- a/src/NJsonSchema/Annotations/IJsonSchemaExtensionDataAttribute.cs +++ b/src/NJsonSchema/Annotations/IJsonSchemaExtensionDataAttribute.cs @@ -19,4 +19,4 @@ public interface IJsonSchemaExtensionDataAttribute /// Gets the value. object Value { get; } } -} +} \ No newline at end of file diff --git a/src/NJsonSchema/Annotations/ItemsCanBeNullAttribute.cs b/src/NJsonSchema/Annotations/ItemsCanBeNullAttribute.cs index 355f9ba32..95d5d67e8 100644 --- a/src/NJsonSchema/Annotations/ItemsCanBeNullAttribute.cs +++ b/src/NJsonSchema/Annotations/ItemsCanBeNullAttribute.cs @@ -11,7 +11,8 @@ namespace NJsonSchema.Annotations { /// Annotation to specify that array items or dictionary values are nullable. - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.Field)] + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter | AttributeTargets.ReturnValue | + AttributeTargets.Field)] public class ItemsCanBeNullAttribute : Attribute { } diff --git a/src/NJsonSchema/Annotations/JsonSchemaAttribute.cs b/src/NJsonSchema/Annotations/JsonSchemaAttribute.cs index d93ca9c1d..8ce094a48 100644 --- a/src/NJsonSchema/Annotations/JsonSchemaAttribute.cs +++ b/src/NJsonSchema/Annotations/JsonSchemaAttribute.cs @@ -11,7 +11,8 @@ namespace NJsonSchema.Annotations { /// Annotation to specify the JSON Schema type for the given class. - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Struct | AttributeTargets.Parameter | AttributeTargets.ReturnValue)] + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Struct | + AttributeTargets.Parameter | AttributeTargets.ReturnValue)] public class JsonSchemaAttribute : Attribute { /// Initializes a new instance of the class. diff --git a/src/NJsonSchema/Annotations/JsonSchemaExtensionDataAttribute.cs b/src/NJsonSchema/Annotations/JsonSchemaExtensionDataAttribute.cs index a57402382..97ca75367 100644 --- a/src/NJsonSchema/Annotations/JsonSchemaExtensionDataAttribute.cs +++ b/src/NJsonSchema/Annotations/JsonSchemaExtensionDataAttribute.cs @@ -12,7 +12,9 @@ namespace NJsonSchema.Annotations { /// Adds an extension data property to a class or property. /// - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Parameter | AttributeTargets.ReturnValue, AllowMultiple = true)] + [AttributeUsage( + AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Parameter | AttributeTargets.ReturnValue, + AllowMultiple = true)] public class JsonSchemaExtensionDataAttribute : Attribute, IJsonSchemaExtensionDataAttribute { /// Initializes a new instance of the class. diff --git a/src/NJsonSchema/Annotations/JsonSchemaFlattenAttribute.cs b/src/NJsonSchema/Annotations/JsonSchemaFlattenAttribute.cs index f56adbb32..2c9a4aad4 100644 --- a/src/NJsonSchema/Annotations/JsonSchemaFlattenAttribute.cs +++ b/src/NJsonSchema/Annotations/JsonSchemaFlattenAttribute.cs @@ -30,4 +30,4 @@ public JsonSchemaFlattenAttribute(bool flatten) /// Gets or sets a value indicating whether to flatten the given type. public bool Flatten { get; } } -} +} \ No newline at end of file diff --git a/src/NJsonSchema/Annotations/JsonSchemaTypeAttribute.cs b/src/NJsonSchema/Annotations/JsonSchemaTypeAttribute.cs index a7bfac582..73994db9d 100644 --- a/src/NJsonSchema/Annotations/JsonSchemaTypeAttribute.cs +++ b/src/NJsonSchema/Annotations/JsonSchemaTypeAttribute.cs @@ -11,7 +11,9 @@ namespace NJsonSchema.Annotations { /// Specifies the type to use for JSON Schema generation. - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.Property | AttributeTargets.Class, AllowMultiple = false)] + [AttributeUsage( + AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.Property | AttributeTargets.Class, + AllowMultiple = false)] public class JsonSchemaTypeAttribute : Attribute { /// Initializes a new instance of the class. diff --git a/src/NJsonSchema/Annotations/MultipleOfAttribute.cs b/src/NJsonSchema/Annotations/MultipleOfAttribute.cs index 0321a0392..393c0df8d 100644 --- a/src/NJsonSchema/Annotations/MultipleOfAttribute.cs +++ b/src/NJsonSchema/Annotations/MultipleOfAttribute.cs @@ -12,7 +12,7 @@ namespace NJsonSchema.Annotations { /// Attribute to set the multipleOf parameter of a JSON Schema. [AttributeUsage(AttributeTargets.Property)] - public class MultipleOfAttribute : Attribute + public class MultipleOfAttribute : Attribute { /// Initializes a new instance of the class. /// The multipleOf value. @@ -31,4 +31,4 @@ public MultipleOfAttribute(decimal multipleOf) /// Gets the value whose modulo the the JSON value must be zero. public decimal MultipleOf { get; private set; } } -} +} \ No newline at end of file diff --git a/src/NJsonSchema/Collections/ObservableDictionary.cs b/src/NJsonSchema/Collections/ObservableDictionary.cs index d8a1f39ce..6723d58b6 100644 --- a/src/NJsonSchema/Collections/ObservableDictionary.cs +++ b/src/NJsonSchema/Collections/ObservableDictionary.cs @@ -22,7 +22,7 @@ internal class ObservableDictionary : IDictionary, INotifyCollectionChanged, INotifyPropertyChanged, IDictionary #if !LEGACY -, IReadOnlyDictionary + , IReadOnlyDictionary #endif { private IDictionary _dictionary; @@ -71,10 +71,7 @@ public ObservableDictionary(int capacity, IEqualityComparer comparer) } /// Gets the underlying dictonary. - protected IDictionary Dictionary - { - get { return _dictionary; } - } + protected IDictionary Dictionary => _dictionary; /// Adds multiple key-value pairs the the dictionary. /// The key-value pairs. @@ -128,7 +125,8 @@ protected virtual void Insert(TKey key, TValue value, bool add) } Dictionary[key] = value; - OnCollectionChanged(NotifyCollectionChangedAction.Replace, new KeyValuePair(key, value), new KeyValuePair(key, item)); + OnCollectionChanged(NotifyCollectionChangedAction.Replace, new KeyValuePair(key, value), + new KeyValuePair(key, item)); } else { @@ -166,7 +164,8 @@ protected void OnCollectionChanged(NotifyCollectionChangedAction action, KeyValu } } - protected void OnCollectionChanged(NotifyCollectionChangedAction action, KeyValuePair newItem, KeyValuePair oldItem) + protected void OnCollectionChanged(NotifyCollectionChangedAction action, KeyValuePair newItem, + KeyValuePair oldItem) { OnPropertyChanged(); var copy = CollectionChanged; @@ -206,21 +205,15 @@ public bool ContainsKey(TKey key) return Dictionary.ContainsKey(key); } - public ICollection Keys - { - get { return Dictionary.Keys; } - } + public ICollection Keys => Dictionary.Keys; - ICollection IDictionary.Values { get { return ((IDictionary)Dictionary).Values; } } + ICollection IDictionary.Values => ((IDictionary) Dictionary).Values; - ICollection IDictionary.Keys { get { return ((IDictionary)Dictionary).Keys; } } + ICollection IDictionary.Keys => ((IDictionary) Dictionary).Keys; #if !LEGACY - IEnumerable IReadOnlyDictionary.Values - { - get { return Values; } - } + IEnumerable IReadOnlyDictionary.Values => Values; #endif @@ -239,6 +232,7 @@ public virtual bool Remove(TKey key) { OnCollectionChanged(); } + //OnCollectionChanged(NotifyCollectionChangedAction.Remove, new KeyValuePair(key, value)); return removed; } @@ -250,22 +244,16 @@ public bool TryGetValue(TKey key, out TValue value) #if !LEGACY - IEnumerable IReadOnlyDictionary.Keys - { - get { return Keys; } - } + IEnumerable IReadOnlyDictionary.Keys => Keys; #endif - public ICollection Values - { - get { return Dictionary.Values; } - } + public ICollection Values => Dictionary.Values; public TValue this[TKey key] { - get { return Dictionary[key]; } - set { Insert(key, value, false); } + get => Dictionary[key]; + set => Insert(key, value, false); } #endregion @@ -279,7 +267,7 @@ public void Add(KeyValuePair item) void IDictionary.Add(object key, object value) { - Insert((TKey)key, (TValue)value, true); + Insert((TKey) key, (TValue) value, true); } public void Clear() @@ -314,20 +302,20 @@ public void Initialize(IEnumerable keyValuePairs) public bool Contains(object key) { - return ContainsKey((TKey)key); + return ContainsKey((TKey) key); } IDictionaryEnumerator IDictionary.GetEnumerator() { - return ((IDictionary)Dictionary).GetEnumerator(); + return ((IDictionary) Dictionary).GetEnumerator(); } public void Remove(object key) { - Remove((TKey)key); + Remove((TKey) key); } - public bool IsFixedSize { get { return false; } } + public bool IsFixedSize => false; public bool Contains(KeyValuePair item) { @@ -341,26 +329,20 @@ public void CopyTo(KeyValuePair[] array, int arrayIndex) public void CopyTo(Array array, int index) { - ((IDictionary)Dictionary).CopyTo(array, index); + ((IDictionary) Dictionary).CopyTo(array, index); } - public int Count - { - get { return Dictionary.Count; } - } + public int Count => Dictionary.Count; public bool IsSynchronized { get; private set; } public object SyncRoot { get; private set; } - public bool IsReadOnly - { - get { return Dictionary.IsReadOnly; } - } + public bool IsReadOnly => Dictionary.IsReadOnly; object IDictionary.this[object key] { - get { return this[(TKey)key]; } - set { this[(TKey)key] = (TValue)value; } + get => this[(TKey) key]; + set => this[(TKey) key] = (TValue) value; } public bool Remove(KeyValuePair item) @@ -383,7 +365,7 @@ public IEnumerator> GetEnumerator() IEnumerator IEnumerable.GetEnumerator() { - return ((IEnumerable)Dictionary).GetEnumerator(); + return ((IEnumerable) Dictionary).GetEnumerator(); } #endregion @@ -400,4 +382,4 @@ IEnumerator IEnumerable.GetEnumerator() #endregion } -} +} \ No newline at end of file diff --git a/src/NJsonSchema/ConversionUtilities.cs b/src/NJsonSchema/ConversionUtilities.cs index 48fa4bc6c..a7041a758 100644 --- a/src/NJsonSchema/ConversionUtilities.cs +++ b/src/NJsonSchema/ConversionUtilities.cs @@ -6,6 +6,8 @@ // Rico Suter, mail@rsuter.com //----------------------------------------------------------------------- +using System.Globalization; +using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; @@ -27,7 +29,8 @@ public static string ConvertToLowerCamelCase(string input, bool firstCharacterMu return string.Empty; } - input = ConvertDashesToCamelCase((input[0].ToString().ToLowerInvariant() + (input.Length > 1 ? input.Substring(1) : "")) + input = ConvertDashesToCamelCase( + (input[0].ToString().ToLowerInvariant() + (input.Length > 1 ? input.Substring(1) : "")) .Replace(" ", "_") .Replace("/", "_")); @@ -55,7 +58,8 @@ public static string ConvertToUpperCamelCase(string input, bool firstCharacterMu return string.Empty; } - input = ConvertDashesToCamelCase((input[0].ToString().ToUpperInvariant() + (input.Length > 1 ? input.Substring(1) : "")) + input = ConvertDashesToCamelCase( + (input[0].ToString().ToUpperInvariant() + (input.Length > 1 ? input.Substring(1) : "")) .Replace(" ", "_") .Replace("/", "_")); @@ -77,17 +81,39 @@ public static string ConvertToStringLiteral(string input) { switch (c) { - case '\'': literal.Append(@"\'"); break; - case '\"': literal.Append("\\\""); break; - case '\\': literal.Append(@"\\"); break; - case '\0': literal.Append(@"\0"); break; - case '\a': literal.Append(@"\a"); break; - case '\b': literal.Append(@"\b"); break; - case '\f': literal.Append(@"\f"); break; - case '\n': literal.Append(@"\n"); break; - case '\r': literal.Append(@"\r"); break; - case '\t': literal.Append(@"\t"); break; - case '\v': literal.Append(@"\v"); break; + case '\'': + literal.Append(@"\'"); + break; + case '\"': + literal.Append("\\\""); + break; + case '\\': + literal.Append(@"\\"); + break; + case '\0': + literal.Append(@"\0"); + break; + case '\a': + literal.Append(@"\a"); + break; + case '\b': + literal.Append(@"\b"); + break; + case '\f': + literal.Append(@"\f"); + break; + case '\n': + literal.Append(@"\n"); + break; + case '\r': + literal.Append(@"\r"); + break; + case '\t': + literal.Append(@"\t"); + break; + case '\v': + literal.Append(@"\v"); + break; default: // ASCII printable character if (c >= 0x20 && c <= 0x7e) @@ -98,11 +124,13 @@ public static string ConvertToStringLiteral(string input) else { literal.Append(@"\u"); - literal.Append(((int)c).ToString("x4")); + literal.Append(((int) c).ToString("x4")); } + break; } } + return literal.ToString(); } @@ -160,7 +188,62 @@ public static string Singularize(string word) /// The output. public static string Tab(string input, int tabCount) { - return input?.Replace("\n", "\n" + string.Join("", Enumerable.Repeat(" ", tabCount))) ?? string.Empty; + if (input is null) + { + return ""; + } + var stringWriter = new StringWriter(new StringBuilder(input.Length), CultureInfo.CurrentCulture); + Tab(input, tabCount, stringWriter); + return stringWriter.ToString(); + } + + /// Add tabs to the given string. + /// The input. + /// The tab count. + /// Stream to write transformed content into. + /// The output. + public static void Tab(string input, int tabCount, TextWriter writer) + { + var tabString = CreateTabString(tabCount); + AddPrefixToBeginningOfNonEmptyLines(input, tabString, writer); + } + + private static void AddPrefixToBeginningOfNonEmptyLines(string input, string tabString, TextWriter writer) + { + if (tabString.Length == 0) + { + return; + } + + for (var i = 0; i < input.Length; i++) + { + var c = input[i]; + writer.Write(c); + if (c == '\n') + { + // only write if not entirely empty line + var foundNonEmptyBeforeNewLine = false; + for (var j = i + 1; j < input.Length; ++j) + { + var c2 = input[j]; + if (c2 == '\n') + { + break; + } + + if (!char.IsWhiteSpace(c2)) + { + foundNonEmptyBeforeNewLine = true; + break; + } + } + + if (foundNonEmptyBeforeNewLine) + { + writer.Write(tabString); + } + } + } } /// Converts all line breaks in a string into '\n' and removes white spaces. @@ -169,21 +252,48 @@ public static string Tab(string input, int tabCount) /// The output. public static string ConvertCSharpDocs(string input, int tabCount) { - input = input? - .Replace("\r", string.Empty) - .Replace("\n", "\n" + string.Join("", Enumerable.Repeat(" ", tabCount)) + "/// ") - ?? string.Empty; + if (input is null) + { + return ""; + } + var tabString = CreateTabString(tabCount); + input = input + .Replace("\r", string.Empty); + + var stringWriter = new StringWriter(new StringBuilder(input.Length), CultureInfo.CurrentCulture); + AddPrefixToBeginningOfNonEmptyLines(input, tabString + "/// ", stringWriter); // TODO: Support more markdown features here - var xml = new XText(input).ToString(); + var xml = new XText(stringWriter.ToString()).ToString(); return Regex.Replace(xml, @"^( *)/// ", m => m.Groups[1] + "///
", RegexOptions.Multiline); } + private static string CreateTabString(int tabCount) + { + if (tabCount == 0) + { + return ""; + } + + if (tabCount == 1) + { + return " "; + } + + if (tabCount == 2) + { + return " "; + } + + var tabString = new string(' ', 4 * tabCount); + return tabString; + } + private static string ConvertDashesToCamelCase(string input) { var sb = new StringBuilder(); var caseFlag = false; - foreach (char c in input) + foreach (var c in input) { if (c == '-') { @@ -199,6 +309,7 @@ private static string ConvertDashesToCamelCase(string input) sb.Append(c); } } + return sb.ToString(); } } diff --git a/src/NJsonSchema/Generation/DefaultSchemaNameGenerator.cs b/src/NJsonSchema/Generation/DefaultSchemaNameGenerator.cs index 339184577..0005a4c51 100644 --- a/src/NJsonSchema/Generation/DefaultSchemaNameGenerator.cs +++ b/src/NJsonSchema/Generation/DefaultSchemaNameGenerator.cs @@ -8,9 +8,7 @@ using System; using System.Linq; -using System.Text.RegularExpressions; using Namotion.Reflection; -using Newtonsoft.Json; using NJsonSchema.Annotations; namespace NJsonSchema.Generation @@ -39,9 +37,9 @@ public virtual string Generate(Type type) if (nType.Type.IsGenericType) #endif { - return GetName(nType).Split('`').First() + "Of" + + return GetName(nType).Split('`').First() + "Of" + string.Join("And", nType.GenericArguments - .Select(a => Generate(a.OriginalType))); + .Select(a => Generate(a.OriginalType))); } return GetName(nType); diff --git a/src/NJsonSchema/Generation/JsonSchemaGenerator.cs b/src/NJsonSchema/Generation/JsonSchemaGenerator.cs index d6ce61202..ed363be8e 100644 --- a/src/NJsonSchema/Generation/JsonSchemaGenerator.cs +++ b/src/NJsonSchema/Generation/JsonSchemaGenerator.cs @@ -185,7 +185,7 @@ public virtual void Generate(TSchemaType schema, ContextualType con ApplySchemaProcessors(schema, contextualType, schemaResolver); } - /// Generetes a schema directly or referenced for the requested schema type; + /// Generetes a schema directly or referenced for the requested schema type; /// does NOT change nullability. /// The resulted schema type which may reference the actual schema. /// The type of the schema to generate. @@ -201,7 +201,7 @@ public TSchemaType GenerateWithReference( return GenerateWithReferenceAndNullability(contextualType, false, schemaResolver, transformation); } - /// Generetes a schema directly or referenced for the requested schema type; + /// Generetes a schema directly or referenced for the requested schema type; /// also adds nullability if required by looking at the type's . /// The resulted schema type which may reference the actual schema. /// The type of the schema to generate. @@ -934,7 +934,7 @@ private void GenerateProperties(Type type, JsonSchema schema, JsonSchemaResolver } else { - // TODO: Remove this hacky code (used to support serialization of exceptions and restore the old behavior [pre 9.x]) + // TODO: Remove this hacky code (used to support serialization of exceptions and restore the old behavior [pre 9.x]) foreach (var memberInfo in contextualAccessors.Where(m => allowedProperties == null || allowedProperties.Contains(m.Name))) { var attribute = memberInfo.GetContextAttribute(); @@ -1074,7 +1074,7 @@ private JsonSchema GenerateInheritance(ContextualType type, JsonSchema schema, J if (actualSchema.Properties.Any() || requiresSchemaReference) { - // Use allOf inheritance only if the schema is an object with properties + // Use allOf inheritance only if the schema is an object with properties // (not empty class which just inherits from array or dictionary) var baseSchema = Generate(baseType, schemaResolver); @@ -1141,7 +1141,7 @@ private void GenerateInheritanceDiscriminator(Type type, JsonSchema schema, Json { var discriminatorName = TryGetInheritanceDiscriminatorName(discriminatorConverter); - // Existing property can be discriminator only if it has String type + // Existing property can be discriminator only if it has String type if (typeSchema.Properties.TryGetValue(discriminatorName, out var existingProperty)) { if (!existingProperty.ActualTypeSchema.Type.HasFlag(JsonObjectType.Integer) && @@ -1407,15 +1407,8 @@ private void ApplyRangeAttribute(JsonSchema schema, IEnumerable paren private void ApplyTypeExtensionDataAttributes(TSchemaType schema, ContextualType contextualType) where TSchemaType : JsonSchema, new() { - Attribute[] extensionAttributes; - -#if NETSTANDARD1_0 - extensionAttributes = contextualType.OriginalType.GetTypeInfo().GetCustomAttributes().Where(attribute => - attribute.GetType().GetTypeInfo().ImplementedInterfaces.Contains(typeof(IJsonSchemaExtensionDataAttribute))).ToArray(); -#else - extensionAttributes = contextualType.OriginalType.GetTypeInfo().GetCustomAttributes().Where(attribute => - typeof(IJsonSchemaExtensionDataAttribute).IsAssignableFrom(attribute.GetType())).ToArray(); -#endif + var extensionAttributes = contextualType.OriginalType.GetTypeInfo().GetCustomAttributes() + .Where(attribute => attribute is IJsonSchemaExtensionDataAttribute).ToArray(); if (extensionAttributes.Any()) { diff --git a/src/NJsonSchema/Generation/SystemTextJsonUtilities.cs b/src/NJsonSchema/Generation/SystemTextJsonUtilities.cs index 88f86e621..7d27e1631 100644 --- a/src/NJsonSchema/Generation/SystemTextJsonUtilities.cs +++ b/src/NJsonSchema/Generation/SystemTextJsonUtilities.cs @@ -42,7 +42,7 @@ public static JsonSerializerSettings ConvertJsonOptionsToNewtonsoftSettings(dyna return settings; } - internal class SystemTextJsonContractResolver : DefaultContractResolver + private sealed class SystemTextJsonContractResolver : DefaultContractResolver { private readonly dynamic _serializerOptions; @@ -56,9 +56,19 @@ protected override JsonProperty CreateProperty(MemberInfo member, MemberSerializ var attributes = member.GetCustomAttributes(true); var property = base.CreateProperty(member, memberSerialization); - property.Ignored = - attributes.FirstAssignableToTypeNameOrDefault("System.Text.Json.Serialization.JsonIgnoreAttribute", TypeNameStyle.FullName) != null || - attributes.FirstAssignableToTypeNameOrDefault("System.Text.Json.Serialization.JsonExtensionDataAttribute", TypeNameStyle.FullName) != null; + + var propertyIgnored = false; + var jsonIgnoreAttribute = attributes.FirstAssignableToTypeNameOrDefault("System.Text.Json.Serialization.JsonIgnoreAttribute", TypeNameStyle.FullName); + if (jsonIgnoreAttribute != null) + { + var condition = jsonIgnoreAttribute.TryGetPropertyValue("Condition"); + if (condition is null || condition.ToString() == "Always") + { + propertyIgnored = true; + } + } + + property.Ignored = propertyIgnored || attributes.FirstAssignableToTypeNameOrDefault("System.Text.Json.Serialization.JsonExtensionDataAttribute", TypeNameStyle.FullName) != null; if (_serializerOptions.PropertyNamingPolicy != null) { @@ -67,7 +77,7 @@ protected override JsonProperty CreateProperty(MemberInfo member, MemberSerializ dynamic jsonPropertyNameAttribute = attributes .FirstAssignableToTypeNameOrDefault("System.Text.Json.Serialization.JsonPropertyNameAttribute", TypeNameStyle.FullName); - + if (jsonPropertyNameAttribute != null && !string.IsNullOrEmpty(jsonPropertyNameAttribute.Name)) { property.PropertyName = jsonPropertyNameAttribute.Name; diff --git a/src/NJsonSchema/JsonPathUtilities.cs b/src/NJsonSchema/JsonPathUtilities.cs index edf07e703..929d1328e 100644 --- a/src/NJsonSchema/JsonPathUtilities.cs +++ b/src/NJsonSchema/JsonPathUtilities.cs @@ -54,7 +54,7 @@ public static string GetJsonPath(object rootObject, object searchedObject, ICont public static IReadOnlyDictionary GetJsonPaths(object rootObject, IEnumerable searchedObjects, IContractResolver contractResolver) #else - public static IDictionary GetJsonPaths(object rootObject, + public static IDictionary GetJsonPaths(object rootObject, IEnumerable searchedObjects, IContractResolver contractResolver) #endif { @@ -95,22 +95,23 @@ private static bool FindJsonPaths(object obj, Dictionary searche checkedObjects.Add(obj); - if (obj is IDictionary) + var pathAndSeparator = basePath + "/"; + if (obj is IDictionary dictionary) { - foreach (var key in ((IDictionary)obj).Keys) + foreach (var key in dictionary.Keys) { - if (FindJsonPaths(((IDictionary)obj)[key], searchedObjects, basePath + "/" + key, checkedObjects, contractResolver)) + if (FindJsonPaths(dictionary[key], searchedObjects, pathAndSeparator + key, checkedObjects, contractResolver)) { return true; } } } - else if (obj is IEnumerable) + else if (obj is IEnumerable enumerable) { var i = 0; - foreach (var item in (IEnumerable)obj) + foreach (var item in enumerable) { - if (FindJsonPaths(item, searchedObjects, basePath + "/" + i, checkedObjects, contractResolver)) + if (FindJsonPaths(item, searchedObjects, pathAndSeparator + i, checkedObjects, contractResolver)) { return true; } @@ -121,15 +122,14 @@ private static bool FindJsonPaths(object obj, Dictionary searche else { var type = obj.GetType(); - var contract = contractResolver.ResolveContract(type) as JsonObjectContract; - if (contract != null) + if (contractResolver.ResolveContract(type) is JsonObjectContract contract) { foreach (var jsonProperty in contract.Properties.Where(p => !p.Ignored)) { var value = jsonProperty.ValueProvider.GetValue(obj); if (value != null) { - if (FindJsonPaths(value, searchedObjects, basePath + "/" + jsonProperty.PropertyName, checkedObjects, contractResolver)) + if (FindJsonPaths(value, searchedObjects, pathAndSeparator + jsonProperty.PropertyName, checkedObjects, contractResolver)) { return true; } diff --git a/src/NJsonSchema/JsonSchema.cs b/src/NJsonSchema/JsonSchema.cs index b48a42070..52af641fc 100644 --- a/src/NJsonSchema/JsonSchema.cs +++ b/src/NJsonSchema/JsonSchema.cs @@ -85,7 +85,9 @@ public static TSchemaType CreateAnySchema() public static string ToolchainVersion => typeof(JsonSchema).Assembly.GetName().Version + " NET40 (Newtonsoft.Json v" + typeof(JToken).Assembly.GetName().Version + ")"; #else - public static string ToolchainVersion => typeof(JsonSchema).GetTypeInfo().Assembly.GetName().Version + + public static string ToolchainVersion => version; + + private static readonly string version = typeof(JsonSchema).GetTypeInfo().Assembly.GetName().Version + " (Newtonsoft.Json v" + typeof(JToken).GetTypeInfo().Assembly.GetName().Version + ")"; #endif @@ -174,7 +176,7 @@ public static async Task FromUrlAsync(string url, CancellationToken /// The HttpClient.GetAsync API is not available on this platform. public static async Task FromUrlAsync(string url, Func referenceResolverFactory, CancellationToken cancellationToken = default) { - var data = await DynamicApis.HttpGetAsync(url, cancellationToken).ConfigureAwait(false); + var data = await DynamicApis.HttpGetAsync(url, cancellationToken).ConfigureAwait(false); return await FromJsonAsync(data, url, referenceResolverFactory,cancellationToken).ConfigureAwait(false); } @@ -204,7 +206,7 @@ public static async Task FromJsonAsync(string data, string documentP /// The JSON reference resolver factory. /// The cancellation token /// The JSON Schema. - public static async Task FromJsonAsync(string data, string documentPath, Func FromJsonAsync(string data, string documentPath, Func referenceResolverFactory, CancellationToken cancellationToken = default) { return await JsonSchemaSerialization.FromJsonAsync(data, SerializationSchemaType, documentPath, referenceResolverFactory, ContractResolver.Value, cancellationToken).ConfigureAwait(false); @@ -258,7 +260,7 @@ public JsonSchema InheritedSchema } } - /// Gets the inherited/parent schema which may also be inlined + /// Gets the inherited/parent schema which may also be inlined /// (the schema itself if it is a dictionary or array, otherwise ). /// Used for code generation. [JsonIgnore] diff --git a/src/NJsonSchema/Visitors/JsonReferenceVisitorBase.cs b/src/NJsonSchema/Visitors/JsonReferenceVisitorBase.cs index 293c3fbe2..673e419d8 100644 --- a/src/NJsonSchema/Visitors/JsonReferenceVisitorBase.cs +++ b/src/NJsonSchema/Visitors/JsonReferenceVisitorBase.cs @@ -61,13 +61,11 @@ public virtual void Visit(object obj) /// The task. protected virtual void Visit(object obj, string path, string typeNameHint, ISet checkedObjects, Action replacer) { - if (obj == null || checkedObjects.Contains(obj)) + if (obj == null || !checkedObjects.Add(obj)) { return; } - checkedObjects.Add(obj); - if (obj is IJsonReference reference) { var newReference = VisitJsonReference(reference, path, typeNameHint);