neuecc / Utf8Json

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

JsonInputFormatter enums #65

Open timdoke opened 6 years ago

timdoke commented 6 years ago

Is it possible with the utf8json JsonInputFormatter, to have it accept both enums as ints and enums as strings? I don't see any options on the JsonInputFormatter class. On swagger, I'm converting enums to strings.. But, in microservice communication, I want it to be able to use enums as ints..

Jon-Murray commented 6 years ago

Yes, i agree it would be good to see something like the Jil:

    [JilDirective(TreatEnumerationAs = typeof(int))]
timdoke commented 6 years ago

That can be done while setting up the options.. I meant to handle both like newtonsoft does.

neuecc commented 6 years ago

Currently EnumFormatter accepts both.

public enum Fruit
{
    Orange = 0,
    Apple = 1
}

var apple1 = JsonSerializer.Deserialize<Fruit>("\"Apple\"");
var apple2 = JsonSerializer.Deserialize<Fruit>("1");

Console.WriteLine(apple1);
Console.WriteLine(apple2);

// Serialize Option
var v1 = JsonSerializer.Serialize(Fruit.Apple, EnumResolver.Default); // serialize as string
var v2 = JsonSerializer.Serialize(Fruit.Apple, EnumResolver.UnderlyingValue); // serialize as value

Console.WriteLine(Encoding.UTF8.GetString(v1)); // "Apple"
Console.WriteLine(Encoding.UTF8.GetString(v2)); // 1
timdoke commented 6 years ago

Hmm.. I have these lines of code set up inside my startup under services.AddMvcCore..

options.InputFormatters.RemoveType(); options.OutputFormatters.RemoveType();

options.InputFormatters.Add(new JsonInputFormatter()); options.OutputFormatters.Add(new JsonOutputFormatter());

Those formatters are from utf8Json.

In the swagger configuration, I have DescribeAllEnumsAsStrings set with SwaggerGenOptions.

The problem is when I send stuff to the API end point, it thinks the object is null if I serialize it as a value.. If I serialize it as a string, it is fine.. Am I doing something incorrectly on setting up the formatters?

neuecc commented 6 years ago

Please show concrete types and concrete code. Also, a code that can explain the problem more concisely.

timdoke commented 6 years ago

Sure.. The problem is around the way that REST clients are generated with autorest or with the built in vs generator from swagger (if not using .net core). If, from swagger, you choose to serialize enums to string, the FruitPickerGenerated type class is made. I guess the issue (or feature?) is that utf8json is more strongly typed and isn't flexible enough to automatically convert an int to a string (in this case).



namespace Utf8JsonEnumTesting
{
    class Program
    {
        static void Main(string[] args)
        {
            TestCombo();

            Console.WriteLine("done!");
            Console.Read();
        }

        private static void TestCombo()
        {
            Console.WriteLine("Starting TestCombo");

            FruitPicker fp = new FruitPicker();
            fp.Name = "Greg";
            fp.Fruit = Fruit.Apple;

            const string incomingStringFromMicroservice = @"{""Name"":""Greg"",""Fruit"":1}";

            //translates 1 to "1".  Then, when we do a convert to the actual enum value in the project with automapper, and gives a correct object
            var newtonsoftFruit = Newtonsoft.Json.JsonConvert.DeserializeObject<FruitPickerGenerated>(incomingStringFromMicroservice);

            //errors as it is expecting a string instead of 1
            var utf8Fruit = Utf8Json.JsonSerializer.Deserialize<FruitPickerGenerated>(incomingStringFromMicroservice);

        }
    }

    public class FruitPicker
    {
        public string Name { get; set; }
        public Fruit Fruit { get; set; }
    }
    public enum Fruit
    {
        Orange = 0,
        Apple = 1
    }

    public class FruitPickerGenerated
    {
        public string Name { get; set; }
        public string Fruit { get; set; }
    }
}
neuecc commented 6 years ago

Thanks, sample code is nice.

Utf8Json supports int -> enum and string -> enum. You can deserialize var utf8Fruit = Utf8Json.JsonSerializer.Deserialize<FruitPicker>(incomingStringFromMicroservice);

But your example shows int -> string convert. This convert is not supported on default.

You can create custom converter.

public class DurableStringFormatter : IJsonFormatter<string>
{
    public void Serialize(ref JsonWriter writer, string value, IJsonFormatterResolver formatterResolver)
    {
        writer.WriteString(value);
    }

    public string Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
    {
        var token = reader.GetCurrentJsonToken();
        switch (token)
        {
            case JsonToken.Number:
                return reader.ReadDouble().ToString();
            case JsonToken.String:
            case JsonToken.Null:
                return reader.ReadString();
            case JsonToken.True:
            case JsonToken.False:
                return reader.ReadBoolean().ToString();
            default:
                throw new JsonParsingException("Current token is invalid. Token:" + token);
        }
    }
}

and adopt it.

public class FruitPickerGenerated
{
    public string Name { get; set; }
    [JsonFormatter(typeof(DurableStringFormatter))]
    public string Fruit { get; set; }
}

Currently can not replace formatter on global if target type is primitive. I will try to configure it in a while.

timdoke commented 6 years ago

Thanks for the reply. Unfortunately, the class and properties are auto generated and partial properties are not supported. So, once we regenerate the source, it will be lost. So, that isn't really a good solution.. I have a work around by making sure the outgoing httpclient formats the json in the correct fashion... But, it would be nice if I didn't have to do that work around. Thanks!