From dc64fce55098cdf25760a1b92a91e2abfd4a1f22 Mon Sep 17 00:00:00 2001 From: Rico Suter Date: Wed, 16 Dec 2020 16:55:47 +0100 Subject: [PATCH] Implement oneOf inheritance --- .../CSharpGenerator.cs | 8 +- .../CSharpTypeResolver.cs | 13 +- .../Models/ClassTemplateModel.cs | 25 ++- .../DefaultValueGeneratorTests.cs | 68 +++---- .../InheritanceSerializationTests.cs | 1 - .../Models/ClassTemplateModel.cs | 32 +--- .../Models/PropertyModel.cs | 13 +- .../TypeScriptGenerator.cs | 2 +- .../TypeScriptTypeResolver.cs | 21 ++- .../JsonSchemaGraphUtilities.cs | 8 +- .../Models/ClassTemplateModelBase.cs | 31 +++- src/NJsonSchema.Demo/NJsonSchema.Demo.csproj | 1 + src/NJsonSchema.Demo/Program.cs | 9 + .../Generation/ExceptionTypeTests.cs | 2 +- .../Generation/InheritanceTests.cs | 6 +- .../Generation/JsonSchemaGenerator.cs | 10 +- src/NJsonSchema/JsonSchema.Reference.cs | 4 +- src/NJsonSchema/JsonSchema.cs | 94 +--------- src/NJsonSchema/JsonSchemaExtensions.cs | 166 ++++++++++++++++++ 19 files changed, 308 insertions(+), 206 deletions(-) create mode 100644 src/NJsonSchema/JsonSchemaExtensions.cs diff --git a/src/NJsonSchema.CodeGeneration.CSharp/CSharpGenerator.cs b/src/NJsonSchema.CodeGeneration.CSharp/CSharpGenerator.cs index dfcb8e047..e087f32df 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/CSharpGenerator.cs +++ b/src/NJsonSchema.CodeGeneration.CSharp/CSharpGenerator.cs @@ -29,7 +29,7 @@ public CSharpGenerator(object rootObject) /// The root object to search for all JSON Schemas. /// The generator settings. public CSharpGenerator(object rootObject, CSharpGeneratorSettings settings) - : this(rootObject, settings, new CSharpTypeResolver(settings)) + : this(rootObject, settings, new CSharpTypeResolver(rootObject, settings)) { } @@ -108,13 +108,13 @@ protected override CodeArtifact GenerateType(JsonSchema schema, string typeNameH } else { - return GenerateClass(schema, typeName); + return GenerateClass(schema, typeName, typeNameHint); } } - private CodeArtifact GenerateClass(JsonSchema schema, string typeName) + private CodeArtifact GenerateClass(JsonSchema schema, string typeName, string typeNameHint) { - var model = new ClassTemplateModel(typeName, Settings, _resolver, schema, RootObject); + var model = new ClassTemplateModel(typeName, typeNameHint, Settings, _resolver, schema, RootObject); RenamePropertyWithSameNameAsClass(typeName, model.Properties); diff --git a/src/NJsonSchema.CodeGeneration.CSharp/CSharpTypeResolver.cs b/src/NJsonSchema.CodeGeneration.CSharp/CSharpTypeResolver.cs index cf0a5eef3..e8ba097a8 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/CSharpTypeResolver.cs +++ b/src/NJsonSchema.CodeGeneration.CSharp/CSharpTypeResolver.cs @@ -14,19 +14,24 @@ namespace NJsonSchema.CodeGeneration.CSharp /// Manages the generated types and converts JSON types to CSharp types. public class CSharpTypeResolver : TypeResolverBase { + private readonly object _rootObject; + /// Initializes a new instance of the class. + /// The root object. /// The generator settings. - public CSharpTypeResolver(CSharpGeneratorSettings settings) - : this(settings, null) + public CSharpTypeResolver(object rootObject, CSharpGeneratorSettings settings) + : this(rootObject, settings, null) { } /// Initializes a new instance of the class. + /// The root object. /// The generator settings. /// The exception type schema. - public CSharpTypeResolver(CSharpGeneratorSettings settings, JsonSchema exceptionSchema) + public CSharpTypeResolver(object rootObject, CSharpGeneratorSettings settings, JsonSchema exceptionSchema) : base(settings) { + _rootObject = rootObject; Settings = settings; ExceptionSchema = exceptionSchema; } @@ -76,7 +81,7 @@ schema is JsonSchemaProperty property && } if (schema.ActualTypeSchema.IsAnyType && - schema.InheritedSchema == null && // not in inheritance hierarchy + schema.GetInheritedSchema(_rootObject) == null && // not in inheritance hierarchy schema.AllOf.Count == 0 && !Types.Keys.Contains(schema) && !schema.HasReference) diff --git a/src/NJsonSchema.CodeGeneration.CSharp/Models/ClassTemplateModel.cs b/src/NJsonSchema.CodeGeneration.CSharp/Models/ClassTemplateModel.cs index aaf4a71e9..a1ecf5b45 100644 --- a/src/NJsonSchema.CodeGeneration.CSharp/Models/ClassTemplateModel.cs +++ b/src/NJsonSchema.CodeGeneration.CSharp/Models/ClassTemplateModel.cs @@ -17,20 +17,23 @@ public class ClassTemplateModel : ClassTemplateModelBase { private readonly CSharpTypeResolver _resolver; private readonly JsonSchema _schema; + private readonly object _rootObject; private readonly CSharpGeneratorSettings _settings; /// Initializes a new instance of the class. /// Name of the type. + /// The name to compare the discriminator against. /// The settings. /// The resolver. /// The schema. /// The root object. - public ClassTemplateModel(string typeName, CSharpGeneratorSettings settings, + public ClassTemplateModel(string typeName, string discriminatorName, CSharpGeneratorSettings settings, CSharpTypeResolver resolver, JsonSchema schema, object rootObject) - : base(resolver, schema, rootObject) + : base(resolver, schema, rootObject, discriminatorName) { _resolver = resolver; _schema = schema; + _rootObject = rootObject; _settings = settings; ClassName = typeName; @@ -39,9 +42,10 @@ public ClassTemplateModel(string typeName, CSharpGeneratorSettings settings, .Select(property => new PropertyModel(this, property, _resolver, _settings)) .ToArray(); - if (schema.InheritedSchema != null) + var inheritedSchema = schema.GetInheritedSchema(rootObject); + if (inheritedSchema != null) { - BaseClass = new ClassTemplateModel(BaseClassName, settings, resolver, schema.InheritedSchema, rootObject); + BaseClass = new ClassTemplateModel(BaseClassName, discriminatorName, settings, resolver, inheritedSchema, rootObject); AllProperties = Properties.Concat(BaseClass.AllProperties).ToArray(); } else @@ -102,12 +106,6 @@ public ClassTemplateModel(string typeName, CSharpGeneratorSettings settings, /// Gets a value indicating whether to render ToJson() and FromJson() methods. public bool GenerateJsonMethods => _settings.GenerateJsonMethods; - /// Gets a value indicating whether the class has discriminator property. - public bool HasDiscriminator => !string.IsNullOrEmpty(_schema.ActualDiscriminator); - - /// Gets the discriminator property name. - public string Discriminator => _schema.ActualDiscriminator; - /// Gets a value indicating whether this class represents a tuple. public bool IsTuple => _schema.ActualTypeSchema.IsTuple; @@ -116,11 +114,8 @@ public ClassTemplateModel(string typeName, CSharpGeneratorSettings settings, .Select(i => _resolver.Resolve(i, i.IsNullable(_settings.SchemaType), string.Empty, false)) .ToArray(); - /// Gets a value indicating whether the class has a parent class. - public bool HasInheritance => _schema.InheritedTypeSchema != null; - /// Gets the base class name. - public string BaseClassName => HasInheritance ? _resolver.Resolve(_schema.InheritedTypeSchema, false, string.Empty, false) + public string BaseClassName => HasInheritance ? _resolver.Resolve(_schema.GetInheritedSchema(_rootObject), false, string.Empty, false) .Replace(_settings.ArrayType + "<", _settings.ArrayBaseType + "<") .Replace(_settings.DictionaryType + "<", _settings.DictionaryBaseType + "<") : null; @@ -129,7 +124,7 @@ public ClassTemplateModel(string typeName, CSharpGeneratorSettings settings, /// Gets a value indicating whether the class inherits from exception. public bool InheritsExceptionSchema => _resolver.ExceptionSchema != null && - _schema?.InheritsSchema(_resolver.ExceptionSchema) == true; + _schema?.InheritsSchema(_resolver.ExceptionSchema, _rootObject) == true; /// Gets a value indicating whether to use the DateFormatConverter. public bool UseDateFormatConverter => _settings.DateType.StartsWith("System.Date"); diff --git a/src/NJsonSchema.CodeGeneration.Tests/DefaultValueGeneratorTests.cs b/src/NJsonSchema.CodeGeneration.Tests/DefaultValueGeneratorTests.cs index ceaf3f07e..be38f380b 100644 --- a/src/NJsonSchema.CodeGeneration.Tests/DefaultValueGeneratorTests.cs +++ b/src/NJsonSchema.CodeGeneration.Tests/DefaultValueGeneratorTests.cs @@ -6,20 +6,19 @@ namespace NJsonSchema.CodeGeneration.Tests { public class DefaultValueGeneratorTests { - private readonly CSharpValueGenerator _csharpGenerator; - private readonly CSharpTypeResolver _csharpTypeResolver; + private readonly CSharpGeneratorSettings _csharpSettings; + private readonly CSharpValueGenerator _csharpValueGenerator; - private readonly TypeScriptValueGenerator _typescriptGenerator; private readonly TypeScriptGeneratorSettings _typescriptSettings; + private readonly TypeScriptValueGenerator _typescriptValueGenerator; public DefaultValueGeneratorTests() { - var csharpSettings = new CSharpGeneratorSettings(); - _csharpTypeResolver = new CSharpTypeResolver(csharpSettings); - _csharpGenerator = new CSharpValueGenerator(csharpSettings); + _csharpSettings = new CSharpGeneratorSettings(); + _csharpValueGenerator = new CSharpValueGenerator(_csharpSettings); _typescriptSettings = new TypeScriptGeneratorSettings(); - _typescriptGenerator = new TypeScriptValueGenerator(_typescriptSettings); + _typescriptValueGenerator = new TypeScriptValueGenerator(_typescriptSettings); } [Fact] @@ -34,10 +33,10 @@ public void When_schema_has_default_value_of_int_it_is_generated_in_CSharp_and_T Default = (int)6 }; - var typescriptTypeResolver = new TypeScriptTypeResolver(_typescriptSettings); + var typescriptTypeResolver = new TypeScriptTypeResolver(schema, _typescriptSettings); - var csharpValue = _csharpGenerator.GetDefaultValue(schema, true, "int", "int", true, _csharpTypeResolver); - var typescriptValue = _typescriptGenerator.GetDefaultValue(schema, true, "int", "int", true, typescriptTypeResolver); + var csharpValue = _csharpValueGenerator.GetDefaultValue(schema, true, "int", "int", true, new CSharpTypeResolver(schema, _csharpSettings)); + var typescriptValue = _typescriptValueGenerator.GetDefaultValue(schema, true, "int", "int", true, typescriptTypeResolver); //// Assert Assert.Equal("6", csharpValue); @@ -57,10 +56,10 @@ public void When_schema_has_default_value_of_long_it_is_generated_in_CSharp_and_ Default = 6000000000L }; - var typescriptTypeResolver = new TypeScriptTypeResolver(_typescriptSettings); + var typescriptTypeResolver = new TypeScriptTypeResolver(schema, _typescriptSettings); - var csharpValue = _csharpGenerator.GetDefaultValue(schema, true, "long", "long", true, _csharpTypeResolver); - var typescriptValue = _typescriptGenerator.GetDefaultValue(schema, true, "long", "long", true, typescriptTypeResolver); + var csharpValue = _csharpValueGenerator.GetDefaultValue(schema, true, "long", "long", true, new CSharpTypeResolver(schema, _csharpSettings)); + var typescriptValue = _typescriptValueGenerator.GetDefaultValue(schema, true, "long", "long", true, typescriptTypeResolver); //// Assert Assert.Equal("6000000000L", csharpValue); @@ -80,10 +79,10 @@ public void When_schema_has_default_value_of_double_it_is_generated_in_CSharp_an Default = 1234.567F }; - var typescriptTypeResolver = new TypeScriptTypeResolver(_typescriptSettings); + var typescriptTypeResolver = new TypeScriptTypeResolver(schema, _typescriptSettings); - var csharpValue = _csharpGenerator.GetDefaultValue(schema, true, "double", "double", true, _csharpTypeResolver); - var typescriptValue = _typescriptGenerator.GetDefaultValue(schema, true, "double", "double", true, typescriptTypeResolver); + var csharpValue = _csharpValueGenerator.GetDefaultValue(schema, true, "double", "double", true, new CSharpTypeResolver(schema, _csharpSettings)); + var typescriptValue = _typescriptValueGenerator.GetDefaultValue(schema, true, "double", "double", true, typescriptTypeResolver); //// Assert Assert.Equal("1234.567D", csharpValue); @@ -102,10 +101,10 @@ public void When_schema_has_default_value_of_double_without_format_it_is_generat Default = 1234.567F }; - var typescriptTypeResolver = new TypeScriptTypeResolver(_typescriptSettings); + var typescriptTypeResolver = new TypeScriptTypeResolver(schema, _typescriptSettings); - var csharpValue = _csharpGenerator.GetDefaultValue(schema, true, "double", "double", true, _csharpTypeResolver); - var typescriptValue = _typescriptGenerator.GetDefaultValue(schema, true, "double", "double", true, typescriptTypeResolver); + var csharpValue = _csharpValueGenerator.GetDefaultValue(schema, true, "double", "double", true, new CSharpTypeResolver(schema, _csharpSettings)); + var typescriptValue = _typescriptValueGenerator.GetDefaultValue(schema, true, "double", "double", true, typescriptTypeResolver); //// Assert Assert.Equal("1234.567D", csharpValue); @@ -125,10 +124,10 @@ public void When_schema_has_default_value_of_float_it_is_generated_in_CSharp_and Default = 1234.567F }; - var typescriptTypeResolver = new TypeScriptTypeResolver(_typescriptSettings); + var typescriptTypeResolver = new TypeScriptTypeResolver(schema, _typescriptSettings); - var csharpValue = _csharpGenerator.GetDefaultValue(schema, true, "float", "float", true, _csharpTypeResolver); - var typescriptValue = _typescriptGenerator.GetDefaultValue(schema, true, "float", "float", true, typescriptTypeResolver); + var csharpValue = _csharpValueGenerator.GetDefaultValue(schema, true, "float", "float", true, new CSharpTypeResolver(schema, _csharpSettings)); + var typescriptValue = _typescriptValueGenerator.GetDefaultValue(schema, true, "float", "float", true, typescriptTypeResolver); //// Assert Assert.Equal("1234.567F", csharpValue); @@ -147,10 +146,10 @@ public void When_schema_has_default_value_of_bool_it_is_generated_in_CSharp_and_ Default = true }; - var typescriptTypeResolver = new TypeScriptTypeResolver(_typescriptSettings); + var typescriptTypeResolver = new TypeScriptTypeResolver(schema, _typescriptSettings); - var csharpValue = _csharpGenerator.GetDefaultValue(schema, true, "bool", "bool", true, _csharpTypeResolver); - var typescriptValue = _typescriptGenerator.GetDefaultValue(schema, true, "bool", "bool", true, typescriptTypeResolver); + var csharpValue = _csharpValueGenerator.GetDefaultValue(schema, true, "bool", "bool", true, new CSharpTypeResolver(schema, _csharpSettings)); + var typescriptValue = _typescriptValueGenerator.GetDefaultValue(schema, true, "bool", "bool", true, typescriptTypeResolver); //// Assert Assert.Equal("true", csharpValue); @@ -169,10 +168,10 @@ public void When_schema_has_default_value_of_string_it_is_generated_in_CSharp_an Default = "test\\test\"test\r\ntest" }; - var typescriptTypeResolver = new TypeScriptTypeResolver(_typescriptSettings); + var typescriptTypeResolver = new TypeScriptTypeResolver(schema, _typescriptSettings); - var csharpValue = _csharpGenerator.GetDefaultValue(schema, true, "string", "string", true, _csharpTypeResolver); - var typescriptValue = _typescriptGenerator.GetDefaultValue(schema, true, "string", "string", true, typescriptTypeResolver); + var csharpValue = _csharpValueGenerator.GetDefaultValue(schema, true, "string", "string", true, new CSharpTypeResolver(schema, _csharpSettings)); + var typescriptValue = _typescriptValueGenerator.GetDefaultValue(schema, true, "string", "string", true, typescriptTypeResolver); //// Assert Assert.Equal("\"test\\\\test\\\"test\\r\\ntest\"", csharpValue); @@ -191,11 +190,6 @@ public string Generate(int index, string name, object value, JsonSchema schema) public void When_schema_has_default_value_of_enum_it_is_generated_in_CSharp_and_TypeScript_correctly() { //// Arrange - var csharpSettings = new CSharpGeneratorSettings { EnumNameGenerator = new MyEnumNameGenerator(), Namespace = "Ns" }; - var csharpGenerator = new CSharpValueGenerator(csharpSettings); - var csharpTypeResolver = new CSharpTypeResolver(csharpSettings); - - //// Act var schema = new JsonSchema() { Type = JsonObjectType.String, @@ -207,9 +201,15 @@ public void When_schema_has_default_value_of_enum_it_is_generated_in_CSharp_and_ Default = "Bar" }; + var csharpSettings = new CSharpGeneratorSettings { EnumNameGenerator = new MyEnumNameGenerator(), Namespace = "Ns" }; + var csharpGenerator = new CSharpValueGenerator(csharpSettings); + var csharpTypeResolver = new CSharpTypeResolver(schema, csharpSettings); + + //// Act + var typescriptSettings = new TypeScriptGeneratorSettings { EnumNameGenerator = new MyEnumNameGenerator() }; var typescriptGenerator = new TypeScriptValueGenerator(typescriptSettings); - var typescriptTypeResolver = new TypeScriptTypeResolver(typescriptSettings); + var typescriptTypeResolver = new TypeScriptTypeResolver(schema, typescriptSettings); var csharpValue = csharpGenerator.GetDefaultValue(schema, true, "MyEnum", "MyEnum", true, csharpTypeResolver); var typescriptValue = typescriptGenerator.GetDefaultValue(schema, true, "MyEnum", "MyEnum", true, typescriptTypeResolver); diff --git a/src/NJsonSchema.CodeGeneration.Tests/InheritanceSerializationTests.cs b/src/NJsonSchema.CodeGeneration.Tests/InheritanceSerializationTests.cs index e300829d8..a962fb3a8 100644 --- a/src/NJsonSchema.CodeGeneration.Tests/InheritanceSerializationTests.cs +++ b/src/NJsonSchema.CodeGeneration.Tests/InheritanceSerializationTests.cs @@ -11,7 +11,6 @@ using NJsonSchema.CodeGeneration.TypeScript; using NJsonSchema.Converters; using Xunit; - using System.IO; using System.Reflection; using System.CodeDom.Compiler; diff --git a/src/NJsonSchema.CodeGeneration.TypeScript/Models/ClassTemplateModel.cs b/src/NJsonSchema.CodeGeneration.TypeScript/Models/ClassTemplateModel.cs index 5bdd00710..ab11ace3c 100644 --- a/src/NJsonSchema.CodeGeneration.TypeScript/Models/ClassTemplateModel.cs +++ b/src/NJsonSchema.CodeGeneration.TypeScript/Models/ClassTemplateModel.cs @@ -17,8 +17,8 @@ public class ClassTemplateModel : ClassTemplateModelBase { private readonly TypeScriptGeneratorSettings _settings; private readonly JsonSchema _schema; + private readonly object _rootObject; private readonly TypeScriptTypeResolver _resolver; - private readonly string _discriminatorName; /// Initializes a new instance of the class. /// The type name. @@ -30,38 +30,24 @@ public class ClassTemplateModel : ClassTemplateModelBase public ClassTemplateModel(string typeName, string discriminatorName, TypeScriptGeneratorSettings settings, TypeScriptTypeResolver resolver, JsonSchema schema, object rootObject) - : base(resolver, schema, rootObject) + : base(resolver, schema, rootObject, discriminatorName) { _settings = settings; _schema = schema; + _rootObject = rootObject; _resolver = resolver; - _discriminatorName = discriminatorName; ClassName = typeName; Properties = _schema.ActualProperties.Values - .Where(v => settings.TypeStyle == TypeScriptTypeStyle.Interface || + .Where(v => settings.TypeStyle == TypeScriptTypeStyle.Interface || v.IsInheritanceDiscriminator == false) - .Select(property => new PropertyModel(this, property, ClassName, _resolver, _settings)) + .Select(property => new PropertyModel(this, property, _rootObject, ClassName, _resolver, _settings)) .ToList(); } /// Gets the class name. public override string ClassName { get; } - /// Gets the name for the discriminator check. - public string DiscriminatorName => HasBaseDiscriminator ? - (_schema.ResponsibleDiscriminatorObject.Mapping.FirstOrDefault(m => m.Value.ActualTypeSchema == _schema.ActualTypeSchema).Key ?? _discriminatorName) : - _discriminatorName; - - /// Gets a value indicating whether the class has a discriminator property. - public bool HasDiscriminator => !string.IsNullOrEmpty(_schema.ActualDiscriminator); - - /// Gets a value indicating whether the class or an inherited class has a discriminator property. - public bool HasBaseDiscriminator => _schema.ResponsibleDiscriminatorObject != null; - - /// Gets the class discriminator property name (may be defined in a inherited class). - public string BaseDiscriminator => _schema.ResponsibleDiscriminatorObject?.PropertyName; - /// Gets a value indicating whether the class has description. public bool HasDescription => !(_schema is JsonSchemaProperty) && (!string.IsNullOrEmpty(_schema.Description) || @@ -72,9 +58,6 @@ public ClassTemplateModel(string typeName, string discriminatorName, !string.IsNullOrEmpty(_schema.Description) ? _schema.Description : _schema.ActualTypeSchema.Description); - /// Gets a value indicating whether this class has a parent class. - public bool HasInheritance => InheritedSchema != null && !InheritedSchema.IsDictionary; - /// Gets the inheritance code. public string Inheritance { @@ -111,7 +94,7 @@ public string Inheritance /// Gets a value indicating whether the class inherits from dictionary. public bool HasIndexerProperty => _schema.IsDictionary || - _schema.InheritedSchema?.IsDictionary == true; + _schema.GetInheritedSchema(_rootObject)?.IsDictionary == true; /// Gets the type of the indexer property value. public string IndexerPropertyValueType @@ -145,8 +128,5 @@ public string IndexerPropertyValueType /// Gets a value indicating whether the export keyword should be added to all classes. public bool ExportTypes => _settings.ExportTypes; - - /// Gets the inherited schema. - private JsonSchema InheritedSchema => _schema.InheritedSchema?.ActualSchema; } } \ No newline at end of file diff --git a/src/NJsonSchema.CodeGeneration.TypeScript/Models/PropertyModel.cs b/src/NJsonSchema.CodeGeneration.TypeScript/Models/PropertyModel.cs index 5670f7a58..c00c00113 100644 --- a/src/NJsonSchema.CodeGeneration.TypeScript/Models/PropertyModel.cs +++ b/src/NJsonSchema.CodeGeneration.TypeScript/Models/PropertyModel.cs @@ -20,22 +20,27 @@ public class PropertyModel : PropertyModelBase private readonly string _parentTypeName; private readonly TypeScriptGeneratorSettings _settings; private readonly JsonSchemaProperty _property; + private readonly object _rootObject; private readonly TypeScriptTypeResolver _resolver; /// Initializes a new instance of the class. /// The class template model. /// The property. + /// The root object. /// Name of the parent type. /// The resolver. /// The settings. public PropertyModel( ClassTemplateModel classTemplateModel, - JsonSchemaProperty property, string parentTypeName, + JsonSchemaProperty property, + object rootObject, + string parentTypeName, TypeScriptTypeResolver typeResolver, TypeScriptGeneratorSettings settings) : base(property, classTemplateModel, typeResolver, settings) { _property = property; + _rootObject = rootObject; _resolver = typeResolver; _parentTypeName = parentTypeName; _settings = settings; @@ -70,17 +75,17 @@ public bool SupportsConstructorConversion if (IsArray) { - return _resolver.SupportsConstructorConversion(_property.ActualTypeSchema?.Item) && + return _resolver.SupportsConstructorConversion(_property.ActualTypeSchema?.Item, _rootObject) && _property.ActualTypeSchema?.Item.ActualSchema.Type.HasFlag(JsonObjectType.Object) == true; } if (IsDictionary) { - return _resolver.SupportsConstructorConversion(_property.ActualTypeSchema?.AdditionalPropertiesSchema) && + return _resolver.SupportsConstructorConversion(_property.ActualTypeSchema?.AdditionalPropertiesSchema, _rootObject) && _property.ActualTypeSchema?.AdditionalPropertiesSchema.ActualSchema.Type.HasFlag(JsonObjectType.Object) == true; } - return _resolver.SupportsConstructorConversion(_property) && + return _resolver.SupportsConstructorConversion(_property.ActualTypeSchema, _rootObject) && !_property.ActualTypeSchema.IsTuple; } } diff --git a/src/NJsonSchema.CodeGeneration.TypeScript/TypeScriptGenerator.cs b/src/NJsonSchema.CodeGeneration.TypeScript/TypeScriptGenerator.cs index 53ead3274..d3ebad1ff 100644 --- a/src/NJsonSchema.CodeGeneration.TypeScript/TypeScriptGenerator.cs +++ b/src/NJsonSchema.CodeGeneration.TypeScript/TypeScriptGenerator.cs @@ -31,7 +31,7 @@ public TypeScriptGenerator(JsonSchema schema) /// The root object to search for all JSON Schemas. /// The generator settings. public TypeScriptGenerator(object rootObject, TypeScriptGeneratorSettings settings) - : this(rootObject, settings, new TypeScriptTypeResolver(settings)) + : this(rootObject, settings, new TypeScriptTypeResolver(rootObject, settings)) { } diff --git a/src/NJsonSchema.CodeGeneration.TypeScript/TypeScriptTypeResolver.cs b/src/NJsonSchema.CodeGeneration.TypeScript/TypeScriptTypeResolver.cs index 51a7390f0..9a41bf02e 100644 --- a/src/NJsonSchema.CodeGeneration.TypeScript/TypeScriptTypeResolver.cs +++ b/src/NJsonSchema.CodeGeneration.TypeScript/TypeScriptTypeResolver.cs @@ -14,13 +14,17 @@ namespace NJsonSchema.CodeGeneration.TypeScript /// Manages the generated types and converts JSON types to TypeScript types. public class TypeScriptTypeResolver : TypeResolverBase { + private readonly object _rootObject; + private const string UnionPipe = " | "; /// Initializes a new instance of the class. + /// The root object. /// The settings. - public TypeScriptTypeResolver(TypeScriptGeneratorSettings settings) + public TypeScriptTypeResolver(object rootObject, TypeScriptGeneratorSettings settings) : base(settings) { + _rootObject = rootObject; Settings = settings; } @@ -54,10 +58,11 @@ public override string Resolve(JsonSchema schema, bool isNullable, string typeNa /// Gets a value indicating whether the schema supports constructor conversion. /// The schema. + /// The root object. /// The result. - public bool SupportsConstructorConversion(JsonSchema schema) + public bool SupportsConstructorConversion(JsonSchema schema, object rootObject) { - return schema?.ActualSchema.ResponsibleDiscriminatorObject == null; + return schema?.ActualSchema.GetBaseDiscriminator(rootObject) == null; } /// Checks whether the given schema should generate a type. @@ -85,7 +90,7 @@ private string Resolve(JsonSchema schema, string typeNameHint, bool addInterface // Primitive schemas (no new type) if (schema.ActualTypeSchema.IsAnyType && - schema.InheritedSchema == null && // not in inheritance hierarchy + schema.GetInheritedSchema(_rootObject) == null && // not in inheritance hierarchy schema.AllOf.Count == 0 && !Types.Keys.Contains(schema) && !schema.HasReference) @@ -141,7 +146,7 @@ private string Resolve(JsonSchema schema, string typeNameHint, bool addInterface if (schema.IsDictionary) { var prefix = addInterfacePrefix && - SupportsConstructorConversion(schema.AdditionalPropertiesSchema) && + SupportsConstructorConversion(schema.AdditionalPropertiesSchema, _rootObject) && schema.AdditionalPropertiesSchema?.ActualSchema.Type.HasFlag(JsonObjectType.Object) == true ? "I" : ""; var valueType = ResolveDictionaryValueType(schema, "any"); @@ -189,8 +194,8 @@ private string Resolve(JsonSchema schema, string typeNameHint, bool addInterface return string.Join(UnionPipe, types); } - return (addInterfacePrefix && !schema.ActualTypeSchema.IsEnumeration && SupportsConstructorConversion(schema) ? "I" : "") + - GetOrGenerateTypeName(schema, typeNameHint); + return (addInterfacePrefix && !schema.ActualTypeSchema.IsEnumeration && SupportsConstructorConversion(schema, _rootObject) ? "I" : "") + + base.GetOrGenerateTypeName(schema, typeNameHint); } private string ResolveString(JsonSchema schema, string typeNameHint) @@ -300,7 +305,7 @@ private string ResolveArrayOrTuple(JsonSchema schema, string typeNameHint, bool { var isObject = schema.Item?.ActualSchema.Type.HasFlag(JsonObjectType.Object) == true; var isDictionary = schema.Item?.ActualSchema.IsDictionary == true; - var prefix = addInterfacePrefix && SupportsConstructorConversion(schema.Item) && isObject && !isDictionary ? "I" : ""; + var prefix = addInterfacePrefix && SupportsConstructorConversion(schema.Item, _rootObject) && isObject && !isDictionary ? "I" : ""; if (Settings.UseLeafType) { diff --git a/src/NJsonSchema.CodeGeneration/JsonSchemaGraphUtilities.cs b/src/NJsonSchema.CodeGeneration/JsonSchemaGraphUtilities.cs index d17ab260b..6318c4a5e 100644 --- a/src/NJsonSchema.CodeGeneration/JsonSchemaGraphUtilities.cs +++ b/src/NJsonSchema.CodeGeneration/JsonSchemaGraphUtilities.cs @@ -20,7 +20,7 @@ public static class JsonSchemaGraphUtilities /// public static IDictionary GetDerivedSchemas(this JsonSchema schema, object rootObject) { - var visitor = new DerivedSchemaVisitor(schema); + var visitor = new DerivedSchemaVisitor(schema, rootObject); visitor.Visit(rootObject); return visitor.DerivedSchemas; } @@ -28,17 +28,19 @@ public static IDictionary GetDerivedSchemas(this JsonSchema private class DerivedSchemaVisitor : JsonSchemaVisitorBase { private readonly JsonSchema _baseSchema; + private readonly object _rootObject; public Dictionary DerivedSchemas { get; } = new Dictionary(); - public DerivedSchemaVisitor(JsonSchema baseSchema) + public DerivedSchemaVisitor(JsonSchema baseSchema, object rootObject) { _baseSchema = baseSchema; + _rootObject = rootObject; } protected override JsonSchema VisitSchema(JsonSchema schema, string path, string typeNameHint) { - if (schema.Inherits(_baseSchema) && _baseSchema != schema) + if (schema.Inherits(_baseSchema, _rootObject) && _baseSchema != schema) { DerivedSchemas.Add(schema, typeNameHint); } diff --git a/src/NJsonSchema.CodeGeneration/Models/ClassTemplateModelBase.cs b/src/NJsonSchema.CodeGeneration/Models/ClassTemplateModelBase.cs index 07ac4287e..168954880 100644 --- a/src/NJsonSchema.CodeGeneration/Models/ClassTemplateModelBase.cs +++ b/src/NJsonSchema.CodeGeneration/Models/ClassTemplateModelBase.cs @@ -16,22 +16,45 @@ public abstract class ClassTemplateModelBase : TemplateModelBase { private readonly JsonSchema _schema; private readonly object _rootObject; + private readonly string _discriminatorName; private readonly TypeResolverBase _resolver; /// Initializes a new instance of the class. /// The resolver. /// The schema. /// The root object. - protected ClassTemplateModelBase(TypeResolverBase resolver, JsonSchema schema, object rootObject) + /// The discriminator name. + protected ClassTemplateModelBase(TypeResolverBase resolver, JsonSchema schema, object rootObject, string discriminatorName) { _schema = schema; _rootObject = rootObject; + _discriminatorName = discriminatorName; _resolver = resolver; } /// Gets the class. public abstract string ClassName { get; } + /// Gets the name for the discriminator check. + public string DiscriminatorName => HasBaseDiscriminator ? + (_schema.GetBaseDiscriminator(_rootObject)?.Mapping?.FirstOrDefault(m => m.Value.ActualTypeSchema == _schema.ActualTypeSchema).Key ?? _discriminatorName) : + _discriminatorName; + + /// Gets a value indicating whether the class has a discriminator property. + public bool HasDiscriminator => !string.IsNullOrEmpty(_schema.ActualDiscriminator); + + /// Gets a value indicating whether the class or an inherited class has a discriminator property. + public bool HasBaseDiscriminator => _schema.GetBaseDiscriminator(_rootObject) != null; + + /// Gets the class discriminator property name (may be defined in a inherited class). + public string BaseDiscriminator => _schema.GetBaseDiscriminator(_rootObject)?.PropertyName; + + /// Gets a value indicating whether this class has a parent class. + public bool HasInheritance => InheritedSchema != null && !InheritedSchema.IsDictionary; + + /// Gets the inherited schema. + protected JsonSchema InheritedSchema => _schema.GetInheritedSchema(_rootObject)?.ActualSchema; + /// Gets a value indicating whether this class represents a JSON object with fixed amount of properties. public bool IsObject => _schema.ActualTypeSchema.IsObject; @@ -44,7 +67,7 @@ protected ClassTemplateModelBase(TypeResolverBase resolver, JsonSchema schema, o /// Gets the derived class names (discriminator key/type name). public ICollection DerivedClasses => _schema .GetDerivedSchemas(_rootObject) - .Select(p => new DerivedClassModel(p.Value, p.Key, _schema.ActualSchema.ResponsibleDiscriminatorObject, _resolver)) + .Select(p => new DerivedClassModel(p.Value, p.Key, _schema.ActualSchema.GetBaseDiscriminator(_rootObject), _resolver)) .ToList(); /// The model of a derived class. @@ -52,13 +75,13 @@ public class DerivedClassModel { internal DerivedClassModel(string typeName, JsonSchema schema, OpenApiDiscriminator discriminator, TypeResolverBase resolver) { - var mapping = discriminator.Mapping.SingleOrDefault(m => m.Value.ActualTypeSchema == schema.ActualTypeSchema); + var mapping = discriminator?.Mapping.SingleOrDefault(m => m.Value.ActualTypeSchema == schema.ActualTypeSchema); ClassName = resolver.GetOrGenerateTypeName(schema, typeName); IsAbstract = schema.ActualTypeSchema.IsAbstract; Discriminator = - mapping.Value != null ? mapping.Key : + mapping?.Value != null ? mapping?.Key : !string.IsNullOrEmpty(typeName) ? typeName : ClassName; } diff --git a/src/NJsonSchema.Demo/NJsonSchema.Demo.csproj b/src/NJsonSchema.Demo/NJsonSchema.Demo.csproj index cae83be42..5d9c15c48 100644 --- a/src/NJsonSchema.Demo/NJsonSchema.Demo.csproj +++ b/src/NJsonSchema.Demo/NJsonSchema.Demo.csproj @@ -9,6 +9,7 @@ + diff --git a/src/NJsonSchema.Demo/Program.cs b/src/NJsonSchema.Demo/Program.cs index f3aa0ba70..f06afde33 100644 --- a/src/NJsonSchema.Demo/Program.cs +++ b/src/NJsonSchema.Demo/Program.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading.Tasks; using Newtonsoft.Json.Linq; +using NJsonSchema.CodeGeneration.Tests; using NJsonSchema.Benchmark; namespace NJsonSchema.Demo @@ -13,6 +14,8 @@ public class Program { static void Main(string[] args) { + RunAsync().GetAwaiter().GetResult(); + Console.BufferHeight = 2000; var passes = 0; @@ -72,6 +75,12 @@ static void Main(string[] args) //Console.ReadLine(); } + private static async Task RunAsync() + { + var x = new InheritanceSerializationTests(); + await x.When_schema_contains_discriminator_and_inheritance_hierarchy_then_TypeScript_is_correctly_generated(); + } + private static void RunTest(string file, JObject suite, JToken value, bool expectedResult, ref int fails, ref int passes, ref int exceptions) { try diff --git a/src/NJsonSchema.Tests/Generation/ExceptionTypeTests.cs b/src/NJsonSchema.Tests/Generation/ExceptionTypeTests.cs index 6fbb68672..d8a62d50f 100644 --- a/src/NJsonSchema.Tests/Generation/ExceptionTypeTests.cs +++ b/src/NJsonSchema.Tests/Generation/ExceptionTypeTests.cs @@ -21,7 +21,7 @@ public async Task When_exception_schema_is_generated_then_special_properties_are var json = schema.ToJson(); //// Act - var exceptionSchema = schema.InheritedSchema.ActualSchema; + var exceptionSchema = schema.GetInheritedSchema(schema).ActualSchema; //// Assert Assert.True(schema.ActualProperties.ContainsKey("foo")); diff --git a/src/NJsonSchema.Tests/Generation/InheritanceTests.cs b/src/NJsonSchema.Tests/Generation/InheritanceTests.cs index 21b80756b..630b7aea1 100644 --- a/src/NJsonSchema.Tests/Generation/InheritanceTests.cs +++ b/src/NJsonSchema.Tests/Generation/InheritanceTests.cs @@ -43,7 +43,7 @@ public async Task When_more_properties_are_defined_in_allOf_and_type_none_then_a var schema = await JsonSchema.FromJsonAsync(json); //// Assert - Assert.NotNull(schema.InheritedSchema); + Assert.NotNull(schema.GetInheritedSchema(schema)); Assert.Equal(2, schema.ActualProperties.Count); Assert.True(schema.ActualProperties.ContainsKey("prop1")); Assert.True(schema.ActualProperties.ContainsKey("prop2")); @@ -75,7 +75,7 @@ public async Task When_allOf_schema_is_object_type_then_it_is_an_inherited_schem var schema = await JsonSchema.FromJsonAsync(json); //// Assert - Assert.NotNull(schema.InheritedSchema); + Assert.NotNull(schema.GetInheritedSchema(schema)); Assert.Equal(1, schema.ActualProperties.Count); Assert.True(schema.ActualProperties.ContainsKey("prop1")); } @@ -367,7 +367,7 @@ public async Task When_class_inherits_from_dictionary_then_allOf_contains_base_d Assert.Equal(2, bar.AllOf.Count); Assert.Equal(bar.AllOf.Last(), bar.ActualTypeSchema); - Assert.Equal(bar.AllOf.First(), bar.InheritedSchema); + Assert.Equal(bar.AllOf.First(), bar.GetInheritedSchema(schema)); 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 45fb0b65b..a34ff3eac 100644 --- a/src/NJsonSchema/Generation/JsonSchemaGenerator.cs +++ b/src/NJsonSchema/Generation/JsonSchemaGenerator.cs @@ -541,7 +541,7 @@ protected virtual void GenerateObject(JsonSchema schema, JsonTypeDescription typ schema.IsAbstract = type.GetTypeInfo().IsAbstract; } - GenerateInheritanceDiscriminator(type, rootSchema, schema); + GenerateInheritanceDiscriminator(type, rootSchema, schema, schemaResolver); GenerateKnownTypes(type, schemaResolver); if (Settings.GenerateXmlObjects) @@ -1029,7 +1029,7 @@ private JsonSchema GenerateInheritance(ContextualType type, JsonSchema schema, J GenerateProperties(baseType, schema, schemaResolver); var actualSchema = GenerateInheritance(baseType, schema, schemaResolver); - GenerateInheritanceDiscriminator(baseType, schema, actualSchema ?? schema); + GenerateInheritanceDiscriminator(baseType, schema, actualSchema ?? schema, schemaResolver); } } else @@ -1094,7 +1094,7 @@ private JsonSchema GenerateInheritance(ContextualType type, JsonSchema schema, J GenerateProperties(i, schema, schemaResolver); var actualSchema = GenerateInheritance(i.ToContextualType(), schema, schemaResolver); - GenerateInheritanceDiscriminator(i, schema, actualSchema ?? schema); + GenerateInheritanceDiscriminator(i, schema, actualSchema ?? schema, schemaResolver); } } } @@ -1102,7 +1102,7 @@ private JsonSchema GenerateInheritance(ContextualType type, JsonSchema schema, J return null; } - private void GenerateInheritanceDiscriminator(Type type, JsonSchema schema, JsonSchema typeSchema) + private void GenerateInheritanceDiscriminator(Type type, JsonSchema schema, JsonSchema typeSchema, JsonSchemaResolver schemaResolver) { if (!Settings.GetActualFlattenInheritanceHierarchy(type)) { @@ -1142,7 +1142,7 @@ private void GenerateInheritanceDiscriminator(Type type, JsonSchema schema, Json } else { - var baseDiscriminator = schema.ResponsibleDiscriminatorObject ?? schema.ActualTypeSchema.ResponsibleDiscriminatorObject; + var baseDiscriminator = schema.GetBaseDiscriminator(schemaResolver.RootObject); baseDiscriminator?.AddMapping(type, schema); } } diff --git a/src/NJsonSchema/JsonSchema.Reference.cs b/src/NJsonSchema/JsonSchema.Reference.cs index 182167ffc..467526797 100644 --- a/src/NJsonSchema/JsonSchema.Reference.cs +++ b/src/NJsonSchema/JsonSchema.Reference.cs @@ -130,7 +130,9 @@ private JsonSchema GetActualSchema(IList checkedSchemas) /// Gets the parent object of this object. [JsonIgnore] - object IJsonReference.PossibleRoot => Parent; + object IJsonReference.PossibleRoot => Parent != this && Parent is IJsonReference reference ? + reference.PossibleRoot : + Parent; // TODO: Avoid cycles /// Gets or sets the referenced object. [JsonIgnore] diff --git a/src/NJsonSchema/JsonSchema.cs b/src/NJsonSchema/JsonSchema.cs index 167cde037..5b4bd8dec 100644 --- a/src/NJsonSchema/JsonSchema.cs +++ b/src/NJsonSchema/JsonSchema.cs @@ -19,6 +19,7 @@ using NJsonSchema.Collections; using NJsonSchema.Generation; using NJsonSchema.Infrastructure; +using NJsonSchema.References; using NJsonSchema.Validation; using NJsonSchema.Validation.FormatValidators; @@ -218,87 +219,6 @@ public bool IsBinary } } - /// Gets the inherited/parent schema (most probable base schema in allOf). - /// Used for code generation. - [JsonIgnore] - public JsonSchema InheritedSchema - { - get - { - if (AllOf == null || AllOf.Count == 0 || HasReference) - { - return null; - } - - if (AllOf.Count == 1) - { - return AllOf.First().ActualSchema; - } - - if (AllOf.Any(s => s.HasReference)) - { - return AllOf.First(s => s.HasReference).ActualSchema; - } - - if (AllOf.Any(s => s.Type.HasFlag(JsonObjectType.Object))) - { - return AllOf.First(s => s.Type.HasFlag(JsonObjectType.Object)).ActualSchema; - } - - return AllOf.FirstOrDefault()?.ActualSchema; - } - } - - /// Gets the inherited/parent schema which may also be inlined - /// (the schema itself if it is a dictionary or array, otherwise ). - /// Used for code generation. - [JsonIgnore] - public JsonSchema InheritedTypeSchema - { - get - { - if (ActualTypeSchema.IsDictionary || ActualTypeSchema.IsArray || ActualTypeSchema.IsTuple) - { - return ActualTypeSchema; - } - - return InheritedSchema; - } - } - - /// Gets the list of all inherited/parent schemas. - /// Used for code generation. - [JsonIgnore] -#if !LEGACY - public IReadOnlyCollection AllInheritedSchemas -#else - public ICollection AllInheritedSchemas -#endif - { - get - { - var inheritedSchema = this.InheritedSchema != null ? - new List { this.InheritedSchema } : - new List(); - - return inheritedSchema.Concat(inheritedSchema.SelectMany(s => s.AllInheritedSchemas)).ToList(); - } - } - - /// Determines whether the given schema is the parent schema of this schema (i.e. super/base class). - /// The possible subtype schema. - /// true or false - public bool Inherits(JsonSchema schema) - { - schema = schema.ActualSchema; - return InheritedSchema?.ActualSchema == schema || InheritedSchema?.Inherits(schema) == true; - } - - /// Gets the discriminator or discriminator of an inherited schema (or null). - [JsonIgnore] - public OpenApiDiscriminator ResponsibleDiscriminatorObject => - ActualDiscriminatorObject ?? InheritedSchema?.ActualSchema.ResponsibleDiscriminatorObject; - /// Gets all properties of this schema (i.e. all direct properties and properties from the schemas in allOf which do not have a type). /// Used for code generation. /// Some properties are defined multiple times. @@ -312,7 +232,7 @@ public IDictionary ActualProperties get { var properties = Properties - .Union(AllOf.Where(s => s.ActualSchema != InheritedSchema) + .Union(AllOf.Where(s => s.ActualSchema != this.GetInheritedSchema(((IJsonReference)this).PossibleRoot)) .SelectMany(s => s.ActualSchema.ActualProperties)) .ToList(); @@ -848,16 +768,6 @@ public JToken ToSampleJson() // return json; //} - /// Gets a value indicating whether this schema inherits from the given parent schema. - /// The parent schema. - /// true or false. - public bool InheritsSchema(JsonSchema parentSchema) - { - return parentSchema != null && ActualSchema - .AllInheritedSchemas.Concat(new List { this }) - .Any(s => s.ActualSchema == parentSchema.ActualSchema) == true; - } - /// Validates the given JSON data against this schema. /// The JSON data to validate. /// Custom validators to validate the JSON. diff --git a/src/NJsonSchema/JsonSchemaExtensions.cs b/src/NJsonSchema/JsonSchemaExtensions.cs new file mode 100644 index 000000000..4ad9494cc --- /dev/null +++ b/src/NJsonSchema/JsonSchemaExtensions.cs @@ -0,0 +1,166 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Rico Suter. All rights reserved. +// +// https://github.com/rsuter/NJsonSchema/blob/master/LICENSE.md +// Rico Suter, mail@rsuter.com +//----------------------------------------------------------------------- + +using System.Collections.Generic; +using System.Linq; +using NJsonSchema.Visitors; + +namespace NJsonSchema +{ + /// JSON Schema graph utilities. + public static class JsonSchemaExtensions + { + /// Gets a value indicating whether this schema inherits from the given parent schema. + /// The schema. + /// The possible parent schema. + /// The root object. + /// true or false. + public static bool InheritsSchema(this JsonSchema schema, JsonSchema parentSchema, object rootObject) + { + return parentSchema != null && schema.ActualSchema + .GetAllInheritedSchemas(rootObject) + .Concat(new List { schema }) + .Any(s => s.ActualSchema == parentSchema.ActualSchema) == true; + } + + /// Gets the inherited/parent schema (most probable base schema in allOf). + /// Used for code generation. + public static JsonSchema GetInheritedSchema(this JsonSchema schema, object rootObject) + { + if (schema.HasReference) + return null; + + if (schema.AllOf == null || schema.AllOf.Count == 0) + return rootObject != null ? schema.GetOneOfInheritedSchema(rootObject) : null; + + if (schema.AllOf.Count == 1) + return schema.AllOf.First().ActualSchema; + + if (schema.AllOf.Any(s => s.HasReference && !s.ActualSchema.IsAnyType)) + return schema.AllOf.First(s => s.HasReference && !s.ActualSchema.IsAnyType).ActualSchema; + + if (schema.AllOf.Any(s => s.Type.HasFlag(JsonObjectType.Object) && !s.ActualSchema.IsAnyType)) + return schema.AllOf.First(s => s.Type.HasFlag(JsonObjectType.Object) && !s.ActualSchema.IsAnyType).ActualSchema; + + return schema.AllOf.First(s => !s.ActualSchema.IsAnyType)?.ActualSchema ?? + (rootObject != null ? schema.GetOneOfInheritedSchema(rootObject) : null); + } + + /// Gets the list of all inherited/parent schemas. + /// Used for code generation. +#if !LEGACY + public static IReadOnlyCollection GetAllInheritedSchemas(this JsonSchema schema, object rootObject) +#else + public static ICollection GetAllInheritedSchemas(this JsonSchema schema, object rootObject) +#endif + { + var inheritedSchema = schema.GetInheritedSchema(rootObject) != null ? + new List { schema.GetInheritedSchema(rootObject) } : + new List(); + + return inheritedSchema.Concat(inheritedSchema.SelectMany(s => s.GetAllInheritedSchemas(rootObject))).ToList(); + } + + /// Determines whether the given schema is the parent schema of this schema (i.e. super/base class). + /// The schema. + /// The possible subtype schema. + /// The root object needed to scan oneOf properties. + /// true or false + public static bool Inherits(this JsonSchema schema, JsonSchema inheritedSchema, object rootObject) + { + inheritedSchema = inheritedSchema.ActualSchema; + return schema.GetInheritedSchema(rootObject)?.ActualSchema == inheritedSchema || + schema.GetInheritedSchema(rootObject)?.Inherits(inheritedSchema, rootObject) == true; + } + + /// Gets the discriminator or discriminator of an inherited schema (or null). + public static OpenApiDiscriminator GetBaseDiscriminator(this JsonSchema schema, object rootObject) + { + return schema.DiscriminatorObject ?? schema.GetInheritedSchema(rootObject)?.ActualSchema.GetBaseDiscriminator(rootObject); + } + + /// Gets the derived schemas. + /// The schema. + /// The root object. + /// + public static IDictionary GetDerivedSchemas(this JsonSchema schema, object rootObject) + { + var visitor = new DerivedSchemaVisitor(schema, rootObject); + visitor.Visit(rootObject); + return visitor.DerivedSchemas; + } + + private class DerivedSchemaVisitor : JsonSchemaVisitorBase + { + private readonly JsonSchema _baseSchema; + private readonly object _rootObject; + + public Dictionary DerivedSchemas { get; } = new Dictionary(); + + public DerivedSchemaVisitor(JsonSchema baseSchema, object rootObject) + { + _baseSchema = baseSchema; + _rootObject = rootObject; + } + + protected override JsonSchema VisitSchema(JsonSchema schema, string path, string typeNameHint) + { + if (schema.Inherits(_baseSchema, _rootObject) && _baseSchema != schema) + DerivedSchemas.Add(schema, typeNameHint); + + return schema; + } + } + + + /// Gets the schemas which are inherited via oneOf. + /// The schema. + /// The root object. + /// + public static JsonSchema GetOneOfInheritedSchema(this JsonSchema schema, object rootObject) + { + return schema.GetOneOfInheritedSchemas(rootObject)?.FirstOrDefault().Key; + } + + /// Gets the schemas which are inherited via oneOf. + /// The schema. + /// The root object. + /// + public static IDictionary GetOneOfInheritedSchemas(this JsonSchema schema, object rootObject) + { + var visitor = new InheritedSchemaVisitor(schema); + visitor.Visit(rootObject); + return visitor.InheritedSchemas; + } + + private class InheritedSchemaVisitor : JsonSchemaVisitorBase + { + private readonly JsonSchema _schema; + + public Dictionary InheritedSchemas { get; } = new Dictionary(); + + public InheritedSchemaVisitor(JsonSchema schema) + { + _schema = schema; + } + + protected override JsonSchema VisitSchema(JsonSchema schema, string path, string typeNameHint) + { + if (schema.OneOf.Any(s => s.ActualSchema == _schema.ActualSchema)) + { + if (schema.OneOf.All(s => s.ActualSchema.Type == JsonObjectType.Object)) + { + InheritedSchemas.Add(schema, typeNameHint); + } + } + + return schema; + } + } + } +} \ No newline at end of file