neuecc / Utf8Json

Definitely Fastest and Zero Allocation JSON Serializer for C#(NET, .NET Core, Unity, Xamarin).
MIT License
2.35k stars 266 forks source link

Custom JsonFormatter in Utf8Json is ignored #194

Open amiry-jd opened 4 years ago

amiry-jd commented 4 years ago

I have this simple JsonFormatter:

public sealed class Int64StringConversionFormatter : IJsonFormatter<long> {

    public void Serialize(ref JsonWriter writer, long value, IJsonFormatterResolver formatterResolver) {
        writer.WriteString(value.ToString(NumberFormatInfo.InvariantInfo));
    }

    public long Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver) {
        var token = reader.GetCurrentJsonToken();

        if (token == JsonToken.String) {
            var s = reader.ReadString();
            return
                long.TryParse(s, NumberStyles.Any, NumberFormatInfo.InvariantInfo, out var l)
                    ? l
                    : 0;
        }

        if (token != JsonToken.Number)
            throw new ValueProviderException("The provided value is not String or Int64.");

        var value = reader.ReadInt64();

        return value;

    }

}

which is an implementation of Utf8Json.IJsonFormatter<> for working with long values. I've added this formatter to an AspNetCore WebApi application like this:

public static MvcOptions SetupCustomJsonFormatter(
    this MvcOptions options) {
    CompositeResolver.RegisterAndSetAsDefault(_formatters, _resolvers);

    options.InputFormatters.Insert(0, new Utf8JsonInputFormatter());
    options.OutputFormatters.Insert(0, new Utf8JsonOutputFormatter());

    return options;
}

And here is my _formatters and _resolvers:

static readonly IJsonFormatterResolver[] resolvers = {
    StandardResolver.ExcludeNullCamelCase, 
    ImmutableCollectionResolver.Instance,
    EnumResolver.Default, 
    DynamicGenericResolver.Instance, 
};

static readonly IJsonFormatter[] _formatters 
    = new [] {new Int64StringConversionFormatter()};

Also, here is my Utf8JsonInputFormatter:

internal sealed class Utf8JsonInputFormatter : IInputFormatter {

    private readonly IJsonFormatterResolver _resolver;

    public Utf8JsonInputFormatter() : this(null) {
    }

    public Utf8JsonInputFormatter(IJsonFormatterResolver resolver) {
        _resolver = resolver ?? JsonSerializer.DefaultResolver;
    }

    public bool CanRead(InputFormatterContext context)
        => context.HttpContext.Request.ContentType?.StartsWith("application/json") == true;

    public async Task<InputFormatterResult> ReadAsync(InputFormatterContext context) {
        var request = context.HttpContext.Request;

        if (request.Body.CanSeek && request.Body.Length == 0) return await InputFormatterResult.NoValueAsync();

        var result = await JsonSerializer.NonGeneric.DeserializeAsync(context.ModelType, request.Body, _resolver);

        return await InputFormatterResult.SuccessAsync(result);
    }

}

Everything seems to should be OK. But Int64StringConversionFormatter.Serialize and Int64StringConversionFormatter.Deserializ methods never get called. I tested the configuration with another simple formatter (say UnixDateTimeFormatter) and it works just fine. But I cannot figure it out why this one isn't getting called. Do you have any idea what am I missing here?

P.S. I also asked the question on stack overflow, but so far nothing.

Achronos-BNG commented 3 years ago

I also encountered a similar problem (I wanted to make it so int64s are serialized as strings and could be deserialized from either a number or a string). However, the formatter was ignored. Checking into the source of the library, I found the problem in https://github.com/neuecc/Utf8Json/blob/master/src/Utf8Json/Resolvers/DynamicObjectResolver.cs, line 507. It looks like there is a list of primitive types that won't call formatters and instead just write directly. I was able to work around this by creating a resolver that, as part of its constructor, uses reflection to locate the static Hashset jsonPrimitives in the type Utf8Json.Resolvers.Internal.DynamicObjectTypeBuilder and remove the entries that I needed to provide custom formatters for. This proved to do the trick and got utf8json to start using my formatter for int64.