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