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

swaggergen not using custom json converter #2500

Open
trampster opened this issue Sep 13, 2022 · 6 comments
Open

swaggergen not using custom json converter #2500

trampster opened this issue Sep 13, 2022 · 6 comments
Labels
help-wanted A change up for grabs for contributions from the community

Comments

@trampster
Copy link

trampster commented Sep 13, 2022

We are using mongodb which has an ObjectId type (struct). We use a JsonConverter to serialize this as a string, This converter is registered using builder.Services.AddJsonOptions. It works perfectly in our actual API.

However when the swagger is generated the examples have it serialised as an object:

"id": {
        "timestamp": 0,
        "creation_time": "2022-09-13T22:36:23.872Z"
      }

Things we have tried which didn't work:

  • using an example tag in the xml comments in the model (this works for other properties but not this one)
    /// <summary>
    /// </summary>
    /// <example>123456456987</example>
    public ObjectId Id { get; set; }
  • using a ISchemaFilter to change the type and set the example (code gets hit but has no effect on example)
public class SwaggerDiversionFilter : ISchemaFilter
{
    public void Apply(OpenApiSchema schema, SchemaFilterContext context)
    {
        foreach(var property in schema.Properties)
        {
            if(context.Type == typeof(Diversion))
            {
                if(property.Key == "id")
                {
                    property.Value.Example = new OpenApiString(new ObjectId().ToString());
                    property.Value.Type = "string";
                }
            }
        }
    }
}
@mrentier
Copy link

mrentier commented Sep 21, 2022

Ran into same problem, different use case. I found two spots I had to band-aid. First is that primitive types are not configurable. I was adding DateOnly, which would be considered a primitive. Following custom JsonSerializerDataContractResolver fixed that for me:

`
internal class CustomJsonSerializerDataContractResolver : ISerializerDataContractResolver
{
private readonly JsonSerializerOptions _serializerOptions;
private readonly JsonSerializerDataContractResolver _resolver;
private static readonly Dictionary<Type, Tuple<DataType, string>> CustomPrimitiveTypesAndFormats = new Dictionary<Type, Tuple<DataType, string>>
{
[typeof(DateOnly)] = Tuple.Create(DataType.String, "date"),
[typeof(TimeOnly)] = Tuple.Create(DataType.String, "time"),
};

    public CustomJsonSerializerDataContractResolver(JsonSerializerOptions serializerOptions)
    {
        _serializerOptions = serializerOptions;
        _resolver = new JsonSerializerDataContractResolver(serializerOptions);
    }

    public DataContract GetDataContractForType(Type type)
    {
        if (type.IsOneOf(typeof(object), typeof(JsonDocument), typeof(JsonElement)))
        {
            return DataContract.ForDynamic(type, JsonConverterFunc);
        }

        if (CustomPrimitiveTypesAndFormats.ContainsKey(type))
        {
            Tuple<DataType, string> tuple = CustomPrimitiveTypesAndFormats[type];
            return DataContract.ForPrimitive(type, tuple.Item1, tuple.Item2, JsonConverterFunc);
        }          
        return _resolver.GetDataContractForType(type);
    }

    private string JsonConverterFunc(object value)
    {
        return JsonSerializer.Serialize(value, _serializerOptions);
    }

    public bool IsSupportedDictionary(Type type, out Type keyType, out Type valueType)
    {
       return _resolver.IsSupportedDictionary(type, out keyType, out valueType);
    }

    public bool IsSupportedCollection(Type type, out Type itemType)
    {
       return _resolver.IsSupportedCollection(type, out itemType);
    }
}

`

Second snag was that JsonOptions didn't get picked up. There is a condition for NetStandard2.0 in the code, which I think needs to be updated so it also includes .NET 6 and NetStandard2.1. Following registration, combined with the custom class above, fixed that:

services.AddTransient<ISerializerDataContractResolver>(s => { var serializerOptions = s.GetService<IOptions<JsonOptions>>()?.Value.JsonSerializerOptions; return new CustomJsonSerializerDataContractResolver(serializerOptions ?? new JsonSerializerOptions()); });

@colotiline
Copy link

services.AddSwaggerGen
(
    _ =>
    {
        _.MapType<ObjectId>
        (
            () => new OpenApiSchema
            {
                Type = "string",
                Example = new OpenApiString("4d0a0684-c98f-4094-9533-022cf61583d8")
            }
        );
    }
);

Copy link
Contributor

This issue is stale because it has been open for 60 days with no activity. It will be automatically closed in 14 days if no further updates are made.

@github-actions github-actions bot added the stale Stale issues or pull requests label Apr 16, 2024
@martincostello
Copy link
Collaborator

#2799 should fix support for DateOnly, and hopefully TimeOnly (haven't checked yet).

If there's something needing tweaking to get the custom converter used correctly we can fix that too.

@martincostello martincostello removed the stale Stale issues or pull requests label Apr 16, 2024
@martincostello
Copy link
Collaborator

I see that both JsonSerializerDataContractResolver and NewtonsoftDataContractResolver have .NET 6+ support for DateOnly and TimeOnly, but System.Text.Json was missing TimeSpan so I'll get that sorted.

I haven't yet found the code path with the missing use of the serializer options though.

@martincostello
Copy link
Collaborator

Right, I understand now. JsonSerializerDataContractResolver (compared to NewtonsoftDataContractResolver) does not delegate to the serializer options to see if it can handle any types that it itself is not capable of dealing with.

We should look at adding the capability to do that if possible, as well as for both types of resolver to allow the user to add custom mappings. That would allow users to self-configure additional types that we don't support yet in the future, e.g. if there was ever a Int256 primitive type added in a future version of .NET.

@martincostello martincostello added the help-wanted A change up for grabs for contributions from the community label Apr 16, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help-wanted A change up for grabs for contributions from the community
Projects
None yet
Development

No branches or pull requests

4 participants