Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generated JSON deserializer fails at runtime when schema enum contains duplicate keys #800

Open
GeorgDangl opened this issue Oct 28, 2018 · 2 comments

Comments

@GeorgDangl
Copy link
Sponsor

The following is the definition of the System.Net.HttpStatusCode type (shortened) from .nuget\packages\netstandard.library\2.0.3\build\netstandard2.0\ref\netstandard.dll:

namespace System.Net
{
    //
    // Summary:
    //     Contains the values of status codes defined for HTTP.
    public enum HttpStatusCode
    {
        //
        // Summary:
        //     Equivalent to HTTP status 300. System.Net.HttpStatusCode.Ambiguous indicates
        //     that the requested information has multiple representations. The default action
        //     is to treat this status as a redirect and follow the contents of the Location
        //     header associated with this response.
        Ambiguous = 300,
        //
        // Summary:
        //     Equivalent to HTTP status 300. System.Net.HttpStatusCode.MultipleChoices indicates
        //     that the requested information has multiple representations. The default action
        //     is to treat this status as a redirect and follow the contents of the Location
        //     header associated with this response.
        MultipleChoices = 300,
    }
}

Per this StackOverflow question (which references the C# spec), this is allowed and correct syntax.

Generating the schema

var jsonSchema = (await NJsonSchema.JsonSchema4.FromTypeAsync(typeof(System.Net.HttpStatusCode))).ToJson();

Produces this (shortened) JSON schema:

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "title": "HttpStatusCode",
  "type": "integer",
  "description": "",
  "x-enumNames": [
    "MultipleChoices",
    "Ambiguous"
  ],
  "enum": [
    300,
    300
  ]
}

After a bit more processing (I'm using a model containing a HttpStatusCode property in a Web API, which gets then converted via NSwag to a Swagger definition, which then again is used to create a C# client) this is the generated C# client code (shortened):

[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.11.0.0 (Newtonsoft.Json v9.0.0.0)")]
    public enum HttpStatusCode
    {   
        [System.Runtime.Serialization.EnumMember(Value = "Ambiguous")]
        MultipleChoices = 14,
    
        [System.Runtime.Serialization.EnumMember(Value = "Ambiguous")]
        Ambiguous = 15
    }

This just fails at runtime when deserializing with Newtonsoft.Json:

System.InvalidOperationException : Enum name 'Ambiguous' already exists on enum 'HttpStatusCode'.

The workaround for me is to simply use the System.Net.HttpStatusCode enum in my generated client instead of the generated one.

@GeorgDangl
Copy link
Sponsor Author

To mitigate this, the following PostProcess works for me in ASP.NET Core:

services.AddSwaggerDocument(c =>
{
    c.PostProcess = (x) =>
    {
        // Some enum classes use multiple integer values for the same value, e.g.
        // System.Net.HttpStatusCode uses these:
        // RedirectKeepVerb = 307
        // TemporaryRedirect = 307
        // MVC is configured to use the StringEnumConverter, and NJsonSchema errorenously
        // outputs the duplicates. For the example above, the value 'TemporaryRedirect' is
        // serialized twice, 'RedirectKeepVerb' is missing.
        // The following post process action should remove duplicated enums
        // See https://github.com/RSuter/NJsonSchema/issues/800 for more information
        foreach (var enumType in x.Definitions.Select(d => d.Value).Where(d => d.IsEnumeration))
        {
            var distinctValues = enumType.Enumeration.Distinct().ToList();
            enumType.Enumeration.Clear();
            foreach (var distinctValue in distinctValues)
            {
                enumType.Enumeration.Add(distinctValue);
            }
        }
    };
});

I've blogged about it here:
https://blog.dangl.me/archive/remove-duplicate-enum-entries-in-swagger-documents-with-nswag-in-aspnet-core/

@RicoSuter RicoSuter added this to the vNext milestone Jan 4, 2019
@RicoSuter
Copy link
Owner

I think you also need to remove the corresponding name in EnumerationNames… otherwise the name-value mapping might be wrong in the generated code. I think this problem should be fixed directly in NJS... i.e. filter out duplicates as you do it manually now...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants