Skip to content

Commit a11b1a8

Browse files
authored
Merge pull request #36 from bonsai-rx/discriminator-dev
Add support for deep oneOf inheritance
2 parents 9ad689f + b493bb9 commit a11b1a8

7 files changed

+85
-36
lines changed

Bonsai.Sgen.Tests/DiscriminatorGenerationTests.cs

+41-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Microsoft.VisualStudio.TestTools.UnitTesting;
22
using NJsonSchema;
3+
using System.Collections.Generic;
34
using System.Linq;
45
using System.Threading.Tasks;
56

@@ -214,10 +215,49 @@ public void GenerateFromArrayItemDiscriminatorRef_EnsureFallbackDiscriminatorBas
214215
var code = generator.GenerateFile();
215216
Assert.IsTrue(code.Contains("class Dog : Animal"), "Derived types do not inherit from base type.");
216217
Assert.IsTrue(!code.Contains("public enum DogKind"), "Discriminator property is repeated in derived types.");
217-
Assert.IsTrue(code.Contains("List<Animal> Animal"), "Container element type does not match base type.");
218+
Assert.IsTrue(code.Contains("List<Animal> Animals"), "Container element type does not match base type.");
218219
Assert.IsTrue(code.Contains("[JsonInheritanceAttribute(\"Dog\", typeof(Dog))]"));
219220
AssertDiscriminatorAttribute(code, serializerLibraries, "kind");
220221
CompilerTestHelper.CompileFromSource(code);
221222
}
223+
224+
[TestMethod]
225+
[DataRow(SerializerLibraries.YamlDotNet)]
226+
[DataRow(SerializerLibraries.NewtonsoftJson)]
227+
[DataRow(SerializerLibraries.NewtonsoftJson | SerializerLibraries.YamlDotNet)]
228+
public void GenerateFromSubDiscriminatorSchemas_InheritanceHierarchyIsPreserved(SerializerLibraries serializerLibraries)
229+
{
230+
var subTypeSchemas = SchemaTestHelper.CreateDerivedSchemas("fur", "long", "short");
231+
var subDiscriminator = SchemaTestHelper.CreateDiscriminatorSchema("fur", subTypeSchemas);
232+
var derivedSchemas = SchemaTestHelper.CreateDerivedSchemas("type", "cat", "dog");
233+
derivedSchemas[0].Value.DiscriminatorObject = subDiscriminator.DiscriminatorObject;
234+
foreach (var subSchema in subDiscriminator.OneOf)
235+
{
236+
derivedSchemas[0].Value.OneOf.Add(subSchema);
237+
}
238+
var discriminator = SchemaTestHelper.CreateDiscriminatorSchema("type", derivedSchemas);
239+
var schema = SchemaTestHelper.CreateContainerSchema(new Dictionary<string, JsonSchema>
240+
{
241+
{ "LongFurCat", subTypeSchemas[0].Value },
242+
{ "Cat", derivedSchemas[0].Value },
243+
{ "Dog", derivedSchemas[1].Value },
244+
{ "Animal", discriminator },
245+
{ "ShortFurCat", subTypeSchemas[1].Value }
246+
});
247+
schema.Properties.Add("Animals", new()
248+
{
249+
Type = JsonObjectType.Array,
250+
Item = new() { Reference = discriminator }
251+
});
252+
253+
var generator = TestHelper.CreateGenerator(schema, serializerLibraries);
254+
var code = generator.GenerateFile();
255+
Assert.IsTrue(code.Contains("class Cat : Animal"), "Derived types do not inherit from base type.");
256+
Assert.IsTrue(!code.Contains("public enum LongFurCatFur"), "Discriminator property is repeated in derived types.");
257+
Assert.IsTrue(code.Contains("List<Animal> Animals"), "Container element type does not match base type.");
258+
Assert.IsTrue(code.Contains("[JsonInheritanceAttribute(\"dog\", typeof(Dog))]"));
259+
AssertDiscriminatorAttribute(code, serializerLibraries, "type");
260+
CompilerTestHelper.CompileFromSource(code);
261+
}
222262
}
223263
}

Bonsai.Sgen.Tests/TestHelper.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ public static CSharpCodeDomGenerator CreateGenerator(
88
JsonSchema schema,
99
SerializerLibraries serializerLibraries = SerializerLibraries.YamlDotNet | SerializerLibraries.NewtonsoftJson)
1010
{
11-
schema = schema.WithUniqueDiscriminatorProperties();
11+
schema = schema.WithResolvedDiscriminatorInheritance();
1212
var settings = new CSharpCodeDomGeneratorSettings
1313
{
1414
Namespace = nameof(TestHelper),

Bonsai.Sgen/CSharpClassTemplate.cs

+4-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Text;
55
using System.Xml.Serialization;
66
using Newtonsoft.Json;
7+
using NJsonSchema;
78
using NJsonSchema.Converters;
89
using YamlDotNet.Serialization;
910

@@ -30,7 +31,7 @@ public override void BuildType(CodeTypeDeclaration type)
3031
var jsonSerializer = Settings.SerializerLibraries.HasFlag(SerializerLibraries.NewtonsoftJson);
3132
var yamlSerializer = Settings.SerializerLibraries.HasFlag(SerializerLibraries.YamlDotNet);
3233
if (Model.IsAbstract) type.TypeAttributes |= System.Reflection.TypeAttributes.Abstract;
33-
if (Model.HasDiscriminator)
34+
if (Model.Schema.DiscriminatorObject is OpenApiDiscriminator discriminator)
3435
{
3536
if (jsonSerializer || yamlSerializer)
3637
{
@@ -39,13 +40,13 @@ public override void BuildType(CodeTypeDeclaration type)
3940
type.CustomAttributes.Add(new CodeAttributeDeclaration(
4041
new CodeTypeReference(typeof(JsonConverter)),
4142
new CodeAttributeArgument(new CodeTypeOfExpression(nameof(JsonInheritanceConverter))),
42-
new CodeAttributeArgument(new CodePrimitiveExpression(Model.Discriminator))));
43+
new CodeAttributeArgument(new CodePrimitiveExpression(discriminator.PropertyName))));
4344
}
4445
if (yamlSerializer)
4546
{
4647
type.CustomAttributes.Add(new CodeAttributeDeclaration(
4748
new CodeTypeReference("YamlDiscriminator"),
48-
new CodeAttributeArgument(new CodePrimitiveExpression(Model.Discriminator))));
49+
new CodeAttributeArgument(new CodePrimitiveExpression(discriminator.PropertyName))));
4950
}
5051

5152
foreach (var derivedModel in Model.DerivedClasses)

Bonsai.Sgen/CSharpCodeDomGenerator.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ public override IEnumerable<CodeArtifact> GenerateTypes()
105105
let classType = type as CSharpClassCodeArtifact
106106
where classType != null
107107
select classType).ToList();
108-
var discriminatorTypes = classTypes.Where(modelType => modelType.Model.HasDiscriminator).ToList();
108+
var discriminatorTypes = classTypes.Where(modelType => modelType.Model.Schema.DiscriminatorObject != null).ToList();
109109
foreach (var type in discriminatorTypes)
110110
{
111111
var matchTemplate = new CSharpTypeMatchTemplate(type, _provider, _options, Settings);

Bonsai.Sgen/CSharpTypeResolver.cs

-5
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,6 @@ public CSharpTypeResolver(CSharpGeneratorSettings settings)
1212
{
1313
}
1414

15-
public CSharpTypeResolver(CSharpGeneratorSettings settings, JsonSchema exceptionSchema)
16-
: base(settings, exceptionSchema)
17-
{
18-
}
19-
2015
public override JsonSchema RemoveNullability(JsonSchema schema)
2116
{
2217
JsonSchema? selectedSchema = null;

Bonsai.Sgen/JsonSchemaExtensions.cs

+37-24
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
using NJsonSchema;
2-
using NJsonSchema.CodeGeneration;
32
using NJsonSchema.Visitors;
43

54
namespace Bonsai.Sgen
65
{
76
internal static class JsonSchemaExtensions
87
{
9-
public static JsonSchema WithUniqueDiscriminatorProperties(this JsonSchema schema)
8+
public static JsonSchema WithResolvedDiscriminatorInheritance(this JsonSchema schema)
109
{
11-
var visitor = new DiscriminatorSchemaVisitor(schema);
12-
visitor.Visit(schema);
10+
var discriminatorVisitor = new DiscriminatorSchemaVisitor(schema);
11+
var derivedDiscriminatorVisitor = new DerivedDiscriminatorSchemaVisitor();
12+
discriminatorVisitor.Visit(schema);
13+
derivedDiscriminatorVisitor.Visit(schema);
1314
return schema;
1415
}
1516

1617
class DiscriminatorSchemaVisitor : JsonSchemaVisitorBase
1718
{
18-
readonly Dictionary<JsonSchema, string> reverseTypeNameLookup = new();
19+
readonly Dictionary<JsonSchema, string> definitionTypeNameLookup = new();
1920

2021
public DiscriminatorSchemaVisitor(JsonSchema rootObject)
2122
{
@@ -34,7 +35,11 @@ private void ResolveOneOfInheritance(JsonSchema schema, JsonSchema baseSchema)
3435
continue;
3536
}
3637

37-
derivedSchema.ActualSchema.AllOf.Add(new JsonSchema { Reference = baseSchema });
38+
var actualSchema = derivedSchema.ActualSchema;
39+
if (!actualSchema.AllOf.Any(schema => schema.Reference == baseSchema))
40+
{
41+
actualSchema.AllOf.Add(new JsonSchema { Reference = baseSchema });
42+
}
3843
}
3944
}
4045

@@ -43,15 +48,16 @@ protected override JsonSchema VisitSchema(JsonSchema schema, string path, string
4348
var actualSchema = schema.ActualSchema;
4449
if (actualSchema.DiscriminatorObject != null)
4550
{
46-
if (schema is JsonSchemaProperty || schema.ParentSchema?.Item == schema)
51+
var isDefinition = definitionTypeNameLookup.TryGetValue(actualSchema, out _);
52+
if (schema is JsonSchemaProperty || schema.ParentSchema?.Item == schema || isDefinition)
4753
{
48-
if (string.IsNullOrEmpty(typeNameHint) &&
49-
!reverseTypeNameLookup.TryGetValue(actualSchema, out typeNameHint))
54+
var discriminatorSchema = isDefinition ? actualSchema : null;
55+
if (string.IsNullOrEmpty(typeNameHint))
5056
{
5157
typeNameHint = "Anonymous";
5258
}
5359

54-
if (!RootObject.Definitions.TryGetValue(typeNameHint, out JsonSchema? discriminatorSchema))
60+
if (discriminatorSchema == null && !RootObject.Definitions.TryGetValue(typeNameHint, out discriminatorSchema))
5561
{
5662
discriminatorSchema = new JsonSchema();
5763
discriminatorSchema.DiscriminatorObject = actualSchema.DiscriminatorObject;
@@ -64,23 +70,13 @@ protected override JsonSchema VisitSchema(JsonSchema schema, string path, string
6470
if (discriminatorSchema.OneOf.Count > 0)
6571
{
6672
ResolveOneOfInheritance(discriminatorSchema, discriminatorSchema);
67-
discriminatorSchema.OneOf.Clear();
6873
}
6974
}
7075

71-
schema.DiscriminatorObject = null;
72-
schema.IsAbstract = false;
73-
return schema;
74-
}
75-
76-
foreach (var derivedSchema in schema.GetDerivedSchemas(RootObject).Keys)
77-
{
78-
foreach (var property in derivedSchema.Properties.Keys.ToList())
76+
if (!isDefinition)
7977
{
80-
if (property == schema.Discriminator)
81-
{
82-
derivedSchema.Properties.Remove(property);
83-
}
78+
actualSchema.DiscriminatorObject = null;
79+
actualSchema.IsAbstract = false;
8480
}
8581
}
8682
}
@@ -111,7 +107,7 @@ private void VisitDefinitions(JsonSchema schema)
111107
{
112108
foreach (var definition in schema.Definitions)
113109
{
114-
reverseTypeNameLookup[definition.Value] = definition.Key;
110+
definitionTypeNameLookup[definition.Value] = definition.Key;
115111
VisitDefinitions(definition.Value);
116112
}
117113
}
@@ -139,5 +135,22 @@ private void VisitDefinitions(IDictionary<string, JsonSchemaProperty> dictionary
139135
}
140136
}
141137
}
138+
139+
class DerivedDiscriminatorSchemaVisitor : JsonSchemaVisitorBase
140+
{
141+
protected override JsonSchema VisitSchema(JsonSchema schema, string path, string typeNameHint)
142+
{
143+
foreach (var baseSchema in schema.AllInheritedSchemas)
144+
{
145+
var discriminatorSchema = baseSchema.DiscriminatorObject;
146+
if (discriminatorSchema != null)
147+
{
148+
schema.Properties.Remove(discriminatorSchema.PropertyName);
149+
}
150+
}
151+
152+
return schema;
153+
}
154+
}
142155
}
143156
}

Bonsai.Sgen/Program.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ static async Task Main(string[] args)
6363
SerializerLibraries = serializerLibraries
6464
};
6565

66-
schema = schema.WithUniqueDiscriminatorProperties();
66+
schema = schema.WithResolvedDiscriminatorInheritance();
6767
var generator = new CSharpCodeDomGenerator(schema, settings);
6868
var code = generator.GenerateFile(generatorTypeName);
6969
if (string.IsNullOrEmpty(outputFilePath))

0 commit comments

Comments
 (0)