Skip to content

Commit 0a21de3

Browse files
committed
Use NSwag's built-in System.Text.Json polymorphic serialization
With tweaks to the polymorphic serialization attribute * UnknownDerivedTypeHandling = System.Text.Json.Serialization.JsonUnknownDerivedTypeHandling.FallBackToBaseType * IgnoreUnrecognizedTypeDiscriminators = true
1 parent 555f63d commit 0a21de3

12 files changed

+57
-620
lines changed

src/Refitter.Core/CSharpClientGeneratorFactory.cs

Lines changed: 17 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public CustomCSharpClientGenerator Create()
2929
{
3030
Namespace = settings.ContractsNamespace ?? settings.Namespace,
3131
JsonLibrary = CSharpJsonLibrary.SystemTextJson,
32+
JsonPolymorphicSerializationStyle = settings.UsePolymorphicSerialization ? CSharpJsonPolymorphicSerializationStyle.SystemTextJson : CSharpJsonPolymorphicSerializationStyle.NJsonSchema,
3233
TypeAccessModifier = settings.TypeAccessibility.ToString().ToLowerInvariant(),
3334
ClassStyle =
3435
settings.ImmutableRecords ||
@@ -46,18 +47,11 @@ public CustomCSharpClientGenerator Create()
4647
csharpClientGeneratorSettings.ParameterNameGenerator = settings.ParameterNameGenerator;
4748
}
4849

49-
csharpClientGeneratorSettings.CSharpGeneratorSettings.TemplateFactory = new CustomTemplateFactory(
50-
csharpClientGeneratorSettings.CSharpGeneratorSettings,
51-
[
52-
typeof(CSharpGenerator).Assembly,
53-
typeof(CSharpGeneratorBaseSettings).Assembly,
54-
typeof(CustomTemplateFactory).Assembly,
55-
]);
50+
csharpClientGeneratorSettings.CSharpGeneratorSettings.TemplateFactory = new CustomTemplateFactory(csharpClientGeneratorSettings.CSharpGeneratorSettings);
5651

5752
var generator = new CustomCSharpClientGenerator(
5853
document,
59-
csharpClientGeneratorSettings,
60-
settings.UsePolymorphicSerialization);
54+
csharpClientGeneratorSettings);
6155

6256
MapCSharpGeneratorSettings(
6357
settings.CodeGeneratorSettings,
@@ -102,39 +96,30 @@ private static void MapCSharpGeneratorSettings(
10296

10397
/// <summary>
10498
/// custom template factory
105-
/// solely for the purpose of supporting UsePolymorphicSerialization
106-
/// This class and its templates should be removed when NSwag supports this feature.
99+
/// solely for the purpose of tweaking the JsonPolymorphic attribute with UnknownDerivedTypeHandling = FallBackToBaseType and IgnoreUnrecognizedTypeDiscriminators = true
100+
/// This class should be removed if NSwag eventually supports setting UnknownDerivedTypeHandling and IgnoreUnrecognizedTypeDiscriminators.
107101
/// </summary>
108102
private class CustomTemplateFactory : NSwag.CodeGeneration.DefaultTemplateFactory
109103
{
110-
/// <summary>Initializes a new instance of the <see cref="DefaultTemplateFactory" /> class.</summary>
104+
/// <summary>Initializes a new instance of the <see cref="CustomTemplateFactory" /> class.</summary>
111105
/// <param name="settings">The settings.</param>
112-
/// <param name="assemblies">The assemblies.</param>
113-
public CustomTemplateFactory(CodeGeneratorSettingsBase settings, Assembly[] assemblies)
114-
: base(settings, assemblies)
106+
public CustomTemplateFactory(CodeGeneratorSettingsBase settings)
107+
: base(settings, [typeof(CSharpGenerator).Assembly, typeof(CSharpGeneratorBaseSettings).Assembly])
115108
{
116109
}
117110

118-
/// <summary>Tries to load an embedded Liquid template.</summary>
119-
/// <param name="language">The language.</param>
120-
/// <param name="template">The template name.</param>
121-
/// <returns>The template.</returns>
111+
/// <inheritdoc />
122112
protected override string GetEmbeddedLiquidTemplate(string language, string template)
123113
{
124-
template = template.TrimEnd('!');
125-
var assembly = Assembly.GetExecutingAssembly(); // this code is running in Refitter.Core and Refitter.SourceGenerator
126-
var resourceName = $"{assembly.GetName().Name}.Templates.{template}.liquid";
127-
128-
var resource = assembly.GetManifestResourceStream(resourceName);
129-
if (resource != null)
114+
var templateText = base.GetEmbeddedLiquidTemplate(language, template);
115+
return template switch
130116
{
131-
using (var reader = new StreamReader(resource))
132-
{
133-
return reader.ReadToEnd();
134-
}
135-
}
136-
137-
return base.GetEmbeddedLiquidTemplate(language, template);
117+
"Class" => templateText
118+
.Replace(
119+
"[System.Text.Json.Serialization.JsonPolymorphic(TypeDiscriminatorPropertyName = \"{{ Discriminator }}\")]",
120+
"[System.Text.Json.Serialization.JsonPolymorphic(TypeDiscriminatorPropertyName = \"{{ Discriminator }}\", UnknownDerivedTypeHandling = System.Text.Json.Serialization.JsonUnknownDerivedTypeHandling.FallBackToBaseType, IgnoreUnrecognizedTypeDiscriminators = true)]"),
121+
_ => templateText,
122+
};
138123
}
139124
}
140125
}
Lines changed: 2 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -1,139 +1,12 @@
1-
using NJsonSchema;
2-
using NJsonSchema.CodeGeneration;
3-
using NJsonSchema.CodeGeneration.CSharp;
4-
using NJsonSchema.CodeGeneration.CSharp.Models;
51
using NSwag;
62
using NSwag.CodeGeneration.CSharp;
73
using NSwag.CodeGeneration.CSharp.Models;
84

95
namespace Refitter.Core;
106

11-
internal class CustomCSharpClientGenerator(OpenApiDocument document, CSharpClientGeneratorSettings settings, bool usePolymorphicSerialization)
12-
#pragma warning disable CS9107 // Parameter is captured into the state of the enclosing type and its value is also passed to the base constructor. The value might be captured by the base class as well.
7+
internal class CustomCSharpClientGenerator(OpenApiDocument document, CSharpClientGeneratorSettings settings)
138
: CSharpClientGenerator(document, settings)
14-
#pragma warning restore CS9107 // Parameter is captured into the state of the enclosing type and its value is also passed to the base constructor. The value might be captured by the base class as well.
159
{
1610
internal CSharpOperationModel CreateOperationModel(OpenApiOperation operation) =>
17-
CreateOperationModel(operation, Settings);
18-
19-
/// <summary>
20-
/// override to generate DTO types with our custom CSharpGenerator
21-
/// This code should be removed when NSwag supports STJ polymorphic serialization
22-
/// </summary>
23-
protected override IEnumerable<CodeArtifact> GenerateDtoTypes()
24-
{
25-
var generator = new CCustomSharpGenerator(document, Settings.CSharpGeneratorSettings, (CSharpTypeResolver)Resolver, usePolymorphicSerialization);
26-
return generator.GenerateTypes();
27-
}
28-
29-
private class CCustomSharpGenerator(OpenApiDocument document, CSharpGeneratorSettings settings, CSharpTypeResolver resolver, bool usePolymorphicSerialization)
30-
#pragma warning disable CS9107 // Parameter is captured into the state of the enclosing type and its value is also passed to the base constructor. The value might be captured by the base class as well.
31-
: CSharpGenerator(document, settings, resolver)
32-
#pragma warning restore CS9107 // Parameter is captured into the state of the enclosing type and its value is also passed to the base constructor. The value might be captured by the base class as well.
33-
{
34-
/// <summary>
35-
/// override to generate Class with our custom ClassTemplateModel
36-
/// code is taken from NJsonSchema.CodeGeneration.CSharp.CSharpGenerator.GenerateType
37-
/// </summary>
38-
protected override CodeArtifact GenerateType(JsonSchema schema, string typeNameHint)
39-
{
40-
var typeName = resolver.GetOrGenerateTypeName(schema, typeNameHint);
41-
42-
if (schema.IsEnumeration)
43-
{
44-
return base.GenerateType(schema, typeName);
45-
}
46-
else
47-
{
48-
return GenerateClass(schema, typeName);
49-
}
50-
}
51-
52-
/// <summary>
53-
/// override to generate JsonInheritanceAttribute, JsonInheritanceConverter with our custom template models
54-
/// code is taken from NJsonSchema.CodeGeneration.CSharp.CSharpGenerator.GenerateTypes
55-
/// </summary>
56-
public override IEnumerable<CodeArtifact> GenerateTypes()
57-
{
58-
var baseArtifacts = base.GenerateTypes();
59-
var artifacts = new List<CodeArtifact>();
60-
61-
if (baseArtifacts.Any(r => r.Code.Contains("JsonInheritanceConverter")))
62-
{
63-
if (Settings.ExcludedTypeNames?.Contains("JsonInheritanceAttribute") != true)
64-
{
65-
var template = Settings.TemplateFactory.CreateTemplate("CSharp", "JsonInheritanceAttribute", new CustomJsonInheritanceConverterTemplateModel(Settings, usePolymorphicSerialization));
66-
artifacts.Add(new CodeArtifact("JsonInheritanceAttribute", CodeArtifactType.Class, CodeArtifactLanguage.CSharp, CodeArtifactCategory.Utility, template));
67-
}
68-
69-
if (Settings.ExcludedTypeNames?.Contains("JsonInheritanceConverter") != true)
70-
{
71-
var template = Settings.TemplateFactory.CreateTemplate("CSharp", "JsonInheritanceConverter", new CustomJsonInheritanceConverterTemplateModel(Settings, usePolymorphicSerialization));
72-
artifacts.Add(new CodeArtifact("JsonInheritanceConverter", CodeArtifactType.Class, CodeArtifactLanguage.CSharp, CodeArtifactCategory.Utility, template));
73-
}
74-
}
75-
76-
return baseArtifacts.Concat(artifacts);
77-
}
78-
79-
/// <summary>
80-
/// Code is taken from NJsonSchema.CodeGeneration.CSharp.CSharpGenerator.GenerateClass
81-
/// to instantiate our custom ClassTemplateModel
82-
/// </summary>
83-
private CodeArtifact GenerateClass(JsonSchema schema, string typeName)
84-
{
85-
var model = new CustomClassTemplateModel(typeName, Settings, resolver, schema, RootObject, usePolymorphicSerialization);
86-
87-
RenamePropertyWithSameNameAsClass(typeName, model.Properties);
88-
89-
var template = Settings.TemplateFactory.CreateTemplate("CSharp", "Class", model);
90-
return new CodeArtifact(typeName, model.BaseClassName, CodeArtifactType.Class, CodeArtifactLanguage.CSharp, CodeArtifactCategory.Contract, template);
91-
}
92-
93-
/// <summary>
94-
/// Code is taken from NJsonSchema.CodeGeneration.CSharp.CSharpGenerator.RenamePropertyWithSameNameAsClass
95-
/// </summary>
96-
private static void RenamePropertyWithSameNameAsClass(string typeName, IEnumerable<PropertyModel> properties)
97-
{
98-
var propertyModels = properties as PropertyModel[] ?? properties.ToArray();
99-
PropertyModel? propertyWithSameNameAsClass = null;
100-
foreach (var p in propertyModels)
101-
{
102-
if (p.PropertyName == typeName)
103-
{
104-
propertyWithSameNameAsClass = p;
105-
break;
106-
}
107-
}
108-
109-
if (propertyWithSameNameAsClass != null)
110-
{
111-
var number = 1;
112-
var candidate = typeName + number;
113-
while (propertyModels.Any(p => p.PropertyName == candidate))
114-
{
115-
number++;
116-
}
117-
118-
propertyWithSameNameAsClass.PropertyName = propertyWithSameNameAsClass.PropertyName + number;
119-
}
120-
}
121-
122-
/// <summary>
123-
/// finally, our custom ClassTemplateModel and CustomJsonInheritanceConverterTemplateModel
124-
/// to have access to UsePolymorphicSerialization
125-
/// This code should be removed when NSwag supports STJ polymorphic serialization
126-
/// </summary>
127-
private class CustomClassTemplateModel(string typeName, CSharpGeneratorSettings settings, CSharpTypeResolver resolver, JsonSchema schema, object rootObject, bool usePolymorphicSerialization)
128-
: ClassTemplateModel(typeName, settings, resolver, schema, rootObject)
129-
{
130-
public bool UsePolymorphicSerialization => usePolymorphicSerialization;
131-
}
132-
133-
private class CustomJsonInheritanceConverterTemplateModel(CSharpGeneratorSettings settings, bool usePolymorphicSerialization)
134-
: JsonInheritanceConverterTemplateModel(settings)
135-
{
136-
public bool UsePolymorphicSerialization => usePolymorphicSerialization;
137-
}
138-
}
11+
base.CreateOperationModel(operation, Settings);
13912
}

src/Refitter.Core/Refitter.Core.csproj

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,4 @@
2323
</AssemblyAttribute>
2424
</ItemGroup>
2525

26-
<ItemGroup>
27-
<EmbeddedResource Include="Templates/*.liquid" />
28-
</ItemGroup>
29-
3026
</Project>

src/Refitter.Core/Templates/Class.liquid

Lines changed: 0 additions & 154 deletions
This file was deleted.

0 commit comments

Comments
 (0)