RicoSuter / NJsonSchema

JSON Schema reader, generator and validator for .NET
http://NJsonSchema.org
MIT License
1.41k stars 536 forks source link

Use native attributes for polymorphism in System.Text.Json #1739

Open marinasundstrom opened 1 month ago

marinasundstrom commented 1 month ago

TL;DR;

When handling polymorphism in System.Text.Json, the generator should emit JsonPolymorphic and JsonDerivedType.

Background

I have been using System.Text.Json as a target library for my projects. And I have discovered an issue with the generated C# client with regard to inheritance.

https://github.com/RicoSuter/NJsonSchema/blob/9bf8f695b373410e8b51e1363270c08dda7b8127/src/NJsonSchema.CodeGeneration.CSharp/Templates/JsonInheritanceConverter.liquid

Some context: Both server and generated C# client use System.Text.Json.

Problem

In the client, the generated special inheritance converter and attributes don't work for me since the serialized JSON always contain a double set of the discriminator.

{"species":"Dog","foo":true,"species":"Dog"}

This is due to the converter adding that. And the serializer doesn't like it.

"The metadata property is either not supported by the type or is not the first property in the deserialized JSON object

Proposal

I have experimented with manually changing the attributes to the build in ones: JsonPolymorphic and JsonDerivedType

So instead of this:

    [JsonInheritanceConverter(typeof(Animal), "species")]
    [JsonInheritanceAttribute("Dog", typeof(AnimalDog))]
    [JsonInheritanceAttribute("Cat", typeof(AnimalCat))]

We should have an option to have this generated when using System.Text.Json:

    [JsonPolymorphic(TypeDiscriminatorPropertyName = "species")]
    [JsonDerivedType(typeof(AnimalDog), "Dog")]
    [JsonDerivedType(typeof(AnimalCat), "Cat")]

That would at least solve my problem.

The changes should be made here:

https://github.com/RicoSuter/NJsonSchema/blob/9bf8f695b373410e8b51e1363270c08dda7b8127/src/NJsonSchema.CodeGeneration.CSharp/Templates/Class.liquid#L8

marinasundstrom commented 1 month ago

My sample project:

https://github.com/marinasundstrom/NullabilityTransformersPrototype/tree/main/WebApi

The Microsoft Open API API doesn't handle inheritance that well, at the moment.

marinasundstrom commented 1 month ago

Some background:

I have been working to make the Open API stack in .NET 9 to work with NSwag client generator the way I expect it to. So I have been talking about its output, because I have tried to create a transformer for dealing with inheritance. Open API in .NET has polymorphism but it is not like in NSwag that deals with type hierarchies.

Just assume that I have been hacking Open API specifications.

I have found that if I, in the base schema/type, set additionalProperties: false then the multiple Discriminators (Species) don't show up. That was lacking in my hack.

Indeed, I checked, NSwag outputs that additionalProperties: false.

It is still a difference in how System.Text.Json deals with it.

That makes me wonder whether additionalProperties: false really should make such i difference in NJsonSchema. It should be assumed to be false by default?

RicoSuter commented 1 week ago

Should now be possible with the new option: https://github.com/RicoSuter/NJsonSchema/pull/1675

Jozzle commented 18 hours ago

@RicoSuter

Thanks a lot for addressing this PolymorphicSerialization issue. For some reason the new setting is not recognized when used as a parameter in the .csproj file?

We are on .Net 8 and use the following command in the .proj file: <Exec WorkingDirectory="$(ProjectDir)" EnvironmentVariables="ASPNETCORE_ENVIRONMENT=Development" Command="$(NSwagExe_Net80) openapi2cscontroller /input:Controllers/specs/openapi-0.1.yaml /classname:Server0_1 /namespace:[REDACTED].PublicApi.Controllers /output:Controllers/generated/ServerController0_1.g.cs /ControllerBaseClass:Microsoft.AspNetCore.Mvc.ControllerBase /JsonLibrary:SystemTextJson /ControllerStyle:abstract /UseActionResultType:true /JsonPolymorphicSerializationStyle:SystemTextJson" />

Error thrown: 3>NSwag bin directory: [REDACTED]nuget\nswag.msbuild\14.2.0\tools\Net80 3>NConsole.UnusedArgumentException: **Unrecognised arguments are present: [/JsonPolymorphicSerializationStyle:SystemTextJson]** 3> at NConsole.CommandLineProcessor.ProcessSingleAsync(String[] args, Object input) 3> at NConsole.CommandLineProcessor.ProcessAsync(String[] args, Object input) 3> at NSwag.Commands.NSwagCommandProcessor.ProcessAsync(String[] args) in /_/src/NSwag.Commands/NSwagCommandProcessor.cs:line 65 3>[REDACTED].PublicApi.csproj(42,9): error MSB3073: The command "dotnet "D:\packages\nuget\nswag.msbuild\14.2.0\buildTransitive\../tools/Net80/dotnet-nswag.dll" openapi2cscontroller /input:Controllers/specs/openapi-0.1.yaml /classname:Server0_1 /namespace:[REDACTED].PublicApi.Controllers /output:Controllers/generated/ServerController0_1.g.cs /ControllerBaseClass:Microsoft.AspNetCore.Mvc.ControllerBase /JsonLibrary:SystemTextJson /ControllerStyle:abstract /UseActionResultType:true /JsonPolymorphicSerializationStyle:SystemTextJson" exited with code -1. 3>Done building project "[REDACTED].PublicApi.csproj" -- FAILED.