diff --git a/src/NJsonSchema.CodeGeneration.CSharp.Tests/GeneralGeneratorTests.cs b/src/NJsonSchema.CodeGeneration.CSharp.Tests/GeneralGeneratorTests.cs index 44742c959..5b6fa72ec 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp.Tests/GeneralGeneratorTests.cs +++ b/src/NJsonSchema.CodeGeneration.CSharp.Tests/GeneralGeneratorTests.cs @@ -278,7 +278,7 @@ public async Task When_class_has_description_then_csharp_has_xml_comment() { //// Arrange var schema = await JsonSchema4.FromTypeAsync(); - schema.Description = "ClassDesc."; + schema.ActualSchema.Description = "ClassDesc."; var generator = new CSharpGenerator(schema); //// Act @@ -295,7 +295,7 @@ public async Task When_property_has_description_then_csharp_has_xml_comment() { //// Arrange var schema = await JsonSchema4.FromTypeAsync(); - schema.Properties["Class"].Description = "PropertyDesc."; + schema.ActualProperties["Class"].Description = "PropertyDesc."; var generator = new CSharpGenerator(schema, new CSharpGeneratorSettings { ClassStyle = CSharpClassStyle.Poco }); //// Act diff --git a/src/NJsonSchema.CodeGeneration.TypeScript.Tests/AbstractGenerationTests.cs b/src/NJsonSchema.CodeGeneration.TypeScript.Tests/AbstractGenerationTests.cs index bda9c81fa..74c8b5a8c 100644 --- a/src/NJsonSchema.CodeGeneration.TypeScript.Tests/AbstractGenerationTests.cs +++ b/src/NJsonSchema.CodeGeneration.TypeScript.Tests/AbstractGenerationTests.cs @@ -19,6 +19,7 @@ public async Task When_class_is_abstract_then_is_abstract_TypeScript_keyword_is_ { /// Arrange var schema = await JsonSchema4.FromTypeAsync(); + var json = schema.ToJson(); /// Act var generator = new TypeScriptGenerator(schema, new TypeScriptGeneratorSettings { TypeScriptVersion = 2.0m }); @@ -26,6 +27,10 @@ public async Task When_class_is_abstract_then_is_abstract_TypeScript_keyword_is_ /// Assert Assert.Contains("export abstract class AbstractClass", code); + + Assert.Contains("base: string", code); + Assert.Contains("super: string", code); + Assert.Contains("foo: string", code); } public class ContainerClass @@ -53,12 +58,12 @@ public async Task When_property_is_required_and_abstract_then_it_is_not_instanti [JsonConverter(typeof(JsonInheritanceConverter))] public class BaseClass { - + public string Base { get; set; } } public class SuperClass : AbstractClass { - + public string Super { get; set; } } [Fact] diff --git a/src/NJsonSchema.CodeGeneration.TypeScript.Tests/TypeScriptGeneratorTests.cs b/src/NJsonSchema.CodeGeneration.TypeScript.Tests/TypeScriptGeneratorTests.cs index 844253b86..5ca3fddbc 100644 --- a/src/NJsonSchema.CodeGeneration.TypeScript.Tests/TypeScriptGeneratorTests.cs +++ b/src/NJsonSchema.CodeGeneration.TypeScript.Tests/TypeScriptGeneratorTests.cs @@ -154,7 +154,9 @@ public async Task When_property_has_description_then_csharp_has_xml_comment() { //// Arrange var schema = await JsonSchema4.FromTypeAsync(); - schema.Properties["Class"].Description = "PropertyDesc."; + schema.ActualProperties["Class"].Description = "PropertyDesc."; + var json = schema.ToJson(); + var generator = new TypeScriptGenerator(schema); //// Act diff --git a/src/NJsonSchema.CodeGeneration/Models/ClassTemplateModelBase.cs b/src/NJsonSchema.CodeGeneration/Models/ClassTemplateModelBase.cs index 1e3eee897..416153c96 100644 --- a/src/NJsonSchema.CodeGeneration/Models/ClassTemplateModelBase.cs +++ b/src/NJsonSchema.CodeGeneration/Models/ClassTemplateModelBase.cs @@ -33,7 +33,7 @@ protected ClassTemplateModelBase(TypeResolverBase resolver, JsonSchema4 schema, public abstract string ClassName { get; } /// Gets or sets a value indicating whether the type is abstract. - public bool IsAbstract => _schema.IsAbstract; + public bool IsAbstract => _schema.ActualTypeSchema.IsAbstract; /// Gets the property extension data. public IDictionary ExtensionData => _schema.ExtensionData; @@ -49,7 +49,7 @@ public class DerivedClassModel { internal DerivedClassModel(string typeName, JsonSchema4 schema, OpenApiDiscriminator discriminator, TypeResolverBase resolver) { - var mapping = discriminator.Mapping.SingleOrDefault(m => m.Value.ActualSchema == schema.ActualSchema); + var mapping = discriminator.Mapping.SingleOrDefault(m => m.Value.ActualTypeSchema == schema.ActualTypeSchema); ClassName = resolver.GetOrGenerateTypeName(schema, typeName); IsAbstract = schema.ActualTypeSchema.IsAbstract; diff --git a/src/NJsonSchema.Tests/Conversion/ArrayTypeToSchemaTests.cs b/src/NJsonSchema.Tests/Conversion/ArrayTypeToSchemaTests.cs index 800a5570a..4b11814a0 100644 --- a/src/NJsonSchema.Tests/Conversion/ArrayTypeToSchemaTests.cs +++ b/src/NJsonSchema.Tests/Conversion/ArrayTypeToSchemaTests.cs @@ -32,7 +32,7 @@ public async Task When_converting_type_inheriting_from_dictionary_then_it_should var data = schema.ToJson(); //// Assert - Assert.Equal(JsonObjectType.Object, schema.Type); + Assert.Equal(JsonObjectType.Object, schema.ActualTypeSchema.Type); Assert.DoesNotContain("Foo", json); Assert.DoesNotContain("foo", json); } diff --git a/src/NJsonSchema.Tests/Generation/ExceptionTypeTests.cs b/src/NJsonSchema.Tests/Generation/ExceptionTypeTests.cs index c2c7d65cc..4e3afc741 100644 --- a/src/NJsonSchema.Tests/Generation/ExceptionTypeTests.cs +++ b/src/NJsonSchema.Tests/Generation/ExceptionTypeTests.cs @@ -24,8 +24,8 @@ public async Task When_exception_schema_is_generated_then_special_properties_are var exceptionSchema = schema.InheritedSchema.ActualSchema; //// Assert - Assert.True(schema.Properties.ContainsKey("foo")); - Assert.True(exceptionSchema.Properties.ContainsKey("InnerException")); + Assert.True(schema.ActualProperties.ContainsKey("foo")); + Assert.True(exceptionSchema.ActualProperties.ContainsKey("InnerException")); } } } diff --git a/src/NJsonSchema.Tests/Generation/InheritanceTests.cs b/src/NJsonSchema.Tests/Generation/InheritanceTests.cs index e7eb9a9c8..9b1157e12 100644 --- a/src/NJsonSchema.Tests/Generation/InheritanceTests.cs +++ b/src/NJsonSchema.Tests/Generation/InheritanceTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.Threading.Tasks; @@ -88,9 +89,9 @@ public async Task When_generating_type_with_inheritance_then_allOf_has_one_item( var schema = await JsonSchema4.FromTypeAsync(); //// Assert - Assert.NotNull(schema.Properties["Class"]); + Assert.NotNull(schema.ActualProperties["Class"]); - Assert.Equal(1, schema.AllOf.Count); + Assert.Equal(2, schema.AllOf.Count); Assert.Contains(schema.Definitions, d => d.Key == "Person"); Assert.NotNull(schema.AllOf.First().ActualSchema.Properties["Name"]); } @@ -298,10 +299,45 @@ public async Task Existing_non_string_property_cant_be_discriminant() //// Arrange //// Act - Task getSchema() => JsonSchema4.FromTypeAsync(); + Task GetSchema() => JsonSchema4.FromTypeAsync(); //// Assert - await Assert.ThrowsAsync(getSchema); + await Assert.ThrowsAsync(GetSchema); + } + + public class Foo + { + public Bar Bar { get; set; } + } + + public class Bar : Dictionary + { + public string Baz { get; set; } + } + + [Fact] + public async Task When_class_inherits_from_dictionary_then_allOf_contains_base_dictionary_schema_and_actual_schema() + { + //// Arrange + var settings = new JsonSchemaGeneratorSettings + { + SchemaType = SchemaType.OpenApi3 + }; + + //// Act + var schema = await JsonSchema4.FromTypeAsync(settings); + var json = schema.ToJson(); + + //// Assert + var bar = schema.Definitions["Bar"]; + + Assert.Equal(2, bar.AllOf.Count); + + Assert.Equal(bar.AllOf.Last(), bar.ActualTypeSchema); + Assert.Equal(bar.AllOf.First(), bar.InheritedSchema); + + Assert.True(bar.AllOf.First().IsDictionary); // base class (dictionary) + Assert.True(bar.AllOf.Last().ActualProperties.Any()); // actual class } } } diff --git a/src/NJsonSchema/Generation/JsonSchemaGenerator.cs b/src/NJsonSchema/Generation/JsonSchemaGenerator.cs index 1947bc34e..6335cfb4d 100644 --- a/src/NJsonSchema/Generation/JsonSchemaGenerator.cs +++ b/src/NJsonSchema/Generation/JsonSchemaGenerator.cs @@ -146,9 +146,7 @@ public virtual async Task GenerateAsync(Type type, IEnumerableGenerates the properties for the given type and schema. - /// The type of the schema type. /// The types. + /// The type description. /// The properties /// The schema resolver. /// The task. - protected virtual async Task GenerateObjectAsync( - Type type, TSchemaType schema, JsonSchemaResolver schemaResolver) - where TSchemaType : JsonSchema4, new() + protected virtual async Task GenerateObjectAsync(Type type, + JsonTypeDescription typeDescription, JsonSchema4 schema, JsonSchemaResolver schemaResolver) { schemaResolver.AddSchema(type, false, schema); + var rootSchema = schema; + + var hasInheritance = await GenerateInheritanceAsync(type, schema, schemaResolver).ConfigureAwait(false); + if (hasInheritance) + { + var actualSchema = new JsonSchema4(); + schema.AllOf.Add(actualSchema); + schema = actualSchema; + } + typeDescription.ApplyType(schema); + schema.Description = await type.GetTypeInfo().GetDescriptionAsync(type.GetTypeInfo().GetCustomAttributes()).ConfigureAwait(false); + schema.AllowAdditionalProperties = false; schema.IsAbstract = type.GetTypeInfo().IsAbstract; - await GeneratePropertiesAndInheritanceAsync(type, schema, schemaResolver).ConfigureAwait(false); + await GeneratePropertiesAsync(type, schema, schemaResolver).ConfigureAwait(false); await ApplyAdditionalPropertiesAsync(type, schema, schemaResolver).ConfigureAwait(false); + GenerateInheritanceDiscriminator(type, rootSchema); + if (Settings.GenerateKnownTypes) await GenerateKnownTypesAsync(type, schemaResolver).ConfigureAwait(false); @@ -510,7 +521,7 @@ private async Task GenerateDictionaryAsync(TSchemaType schema, Type schema.AllowAdditionalProperties = true; } - private async Task GeneratePropertiesAndInheritanceAsync(Type type, JsonSchema4 schema, JsonSchemaResolver schemaResolver) + private async Task GeneratePropertiesAsync(Type type, JsonSchema4 schema, JsonSchemaResolver schemaResolver) { #if !LEGACY var propertiesAndFields = type.GetTypeInfo() @@ -603,8 +614,6 @@ private async Task GeneratePropertiesAndInheritanceAsync(Type type, JsonSchema4 await LoadPropertyOrFieldAsync(property, info, type, schema, schemaResolver).ConfigureAwait(false); } } - - await GenerateInheritanceAsync(type, schema, schemaResolver).ConfigureAwait(false); } /// Gets the properties of the given type or null to take all properties. @@ -656,7 +665,7 @@ private async Task AddKnownTypeAsync(Type type, JsonSchemaResolver schemaResolve await GenerateAsync(type, schemaResolver).ConfigureAwait(false); } - private async Task GenerateInheritanceAsync(Type type, JsonSchema4 schema, JsonSchemaResolver schemaResolver) + private async Task GenerateInheritanceAsync(Type type, JsonSchema4 schema, JsonSchemaResolver schemaResolver) { var baseType = type.GetTypeInfo().BaseType; if (baseType != null && baseType != typeof(object) && baseType != typeof(ValueType)) @@ -669,7 +678,12 @@ private async Task GenerateInheritanceAsync(Type type, JsonSchema4 schema, JsonS { var typeDescription = Settings.ReflectionService.GetDescription(baseType, null, Settings); if (!typeDescription.IsDictionary && !type.IsArray) - await GeneratePropertiesAndInheritanceAsync(baseType, schema, schemaResolver).ConfigureAwait(false); + { + await GeneratePropertiesAsync(baseType, schema, schemaResolver).ConfigureAwait(false); + await GenerateInheritanceAsync(baseType, schema, schemaResolver).ConfigureAwait(false); + + GenerateInheritanceDiscriminator(baseType, schema); + } } else { @@ -687,6 +701,8 @@ private async Task GenerateInheritanceAsync(Type type, JsonSchema4 schema, JsonS } else schema.AllOf.Add(baseSchema); + + return true; } } } @@ -700,12 +716,18 @@ private async Task GenerateInheritanceAsync(Type type, JsonSchema4 schema, JsonS #endif { var typeDescription = Settings.ReflectionService.GetDescription(i, null, Settings); - if (!typeDescription.IsDictionary && !type.IsArray && !typeof(IEnumerable).GetTypeInfo().IsAssignableFrom(i.GetTypeInfo())) - await GeneratePropertiesAndInheritanceAsync(i, schema, schemaResolver).ConfigureAwait(false); + if (!typeDescription.IsDictionary && !type.IsArray && + !typeof(IEnumerable).GetTypeInfo().IsAssignableFrom(i.GetTypeInfo())) + { + await GeneratePropertiesAsync(i, schema, schemaResolver).ConfigureAwait(false); + await GenerateInheritanceAsync(i, schema, schemaResolver).ConfigureAwait(false); + + GenerateInheritanceDiscriminator(i, schema); + } } } - GenerateInheritanceDiscriminator(type, schema); + return false; } private void GenerateInheritanceDiscriminator(Type type, JsonSchema4 schema) @@ -1002,15 +1024,15 @@ private void ApplyRangeAttribute(JsonSchema4 schema, IEnumerable pare { if (rangeAttribute.OperandType == typeof(double)) { - var minimum = (double) Convert.ChangeType(rangeAttribute.Minimum, typeof(double)); + var minimum = (double)Convert.ChangeType(rangeAttribute.Minimum, typeof(double)); if (minimum > double.MinValue) { - schema.Minimum = (decimal) minimum; + schema.Minimum = (decimal)minimum; } } else { - var minimum = (decimal) Convert.ChangeType(rangeAttribute.Minimum, typeof(decimal)); + var minimum = (decimal)Convert.ChangeType(rangeAttribute.Minimum, typeof(decimal)); if (minimum > decimal.MinValue) { schema.Minimum = minimum; @@ -1022,15 +1044,15 @@ private void ApplyRangeAttribute(JsonSchema4 schema, IEnumerable pare { if (rangeAttribute.OperandType == typeof(double)) { - var maximum = (double) Convert.ChangeType(rangeAttribute.Maximum, typeof(double)); + var maximum = (double)Convert.ChangeType(rangeAttribute.Maximum, typeof(double)); if (maximum < double.MaxValue) { - schema.Maximum = (decimal) maximum; + schema.Maximum = (decimal)maximum; } } else { - var maximum = (decimal) Convert.ChangeType(rangeAttribute.Maximum, typeof(decimal)); + var maximum = (decimal)Convert.ChangeType(rangeAttribute.Maximum, typeof(decimal)); if (maximum < decimal.MaxValue) { schema.Maximum = maximum; diff --git a/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs b/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs index 4100d4aaa..d8047a017 100644 --- a/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs +++ b/src/NJsonSchema/Infrastructure/XmlDocumentationExtensions.cs @@ -463,7 +463,11 @@ private static string RemoveLineBreakWhiteSpaces(string documentation) private static string GetMemberElementName(dynamic member) { char prefixCode; - string memberName = member is Type ? ((Type)member).FullName : (member.DeclaringType.FullName + "." + member.Name); + + var memberName = member is Type memberType && !string.IsNullOrEmpty(memberType.FullName) ? + memberType.FullName : + member.DeclaringType.FullName + "." + member.Name; + switch ((string)member.MemberType.ToString()) { case "Constructor": diff --git a/src/NJsonSchema/JsonSchema4.Reference.cs b/src/NJsonSchema/JsonSchema4.Reference.cs index 89028c263..646bc88c5 100644 --- a/src/NJsonSchema/JsonSchema4.Reference.cs +++ b/src/NJsonSchema/JsonSchema4.Reference.cs @@ -8,8 +8,10 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using Newtonsoft.Json; +using NJsonSchema.Collections; using NJsonSchema.References; namespace NJsonSchema @@ -26,7 +28,18 @@ public partial class JsonSchema4 : JsonReferenceBase, IJsonReferenc /// Cyclic references detected. /// The schema reference path has not been resolved. [JsonIgnore] - public virtual JsonSchema4 ActualTypeSchema => OneOf.FirstOrDefault(o => !o.IsNullable(SchemaType.JsonSchema))?.ActualSchema ?? ActualSchema; + public virtual JsonSchema4 ActualTypeSchema + { + get + { + if (AllOf.Count > 1 && AllOf.Count(s => !s.HasReference && !s.IsDictionary) == 1) + { + return AllOf.First(s => !s.HasReference && !s.IsDictionary); + } + + return OneOf.FirstOrDefault(o => !o.IsNullable(SchemaType.JsonSchema))?.ActualSchema ?? ActualSchema; + } + } /// Gets a value indicating whether this is a schema reference ($ref or ). [JsonIgnore]