JamesNK / Newtonsoft.Json

Json.NET is a popular high-performance JSON framework for .NET
https://www.newtonsoft.com/json
MIT License
10.77k stars 3.25k forks source link

Failure to deserialize ulong enum #2301

Open Silnar opened 4 years ago

Silnar commented 4 years ago

Serialization of ulong enum values works fine, but deserialization fails on large values.

Here is an example code that serializes and then deserializes large enum value:

using System;
using Newtonsoft.Json;

public enum Type : ulong
{
    Alpha = 1UL << 1,
    Beta = 1UL << 33,
    Gamma = 1UL << 63,
    Omega = ulong.MaxValue,
}

public class Data
{
    public Type Type;
}

public class Program
{
    public static void Main()
    {
        var data = new Data { Type = Type.Gamma };
        Console.WriteLine("Json: {0}", JsonConvert.SerializeObject(data));

        Console.WriteLine("----");

        var dataJson = String.Format("{{\"Type\":{0}}}", (ulong)data.Type);
        Console.WriteLine("Type: {0}", JsonConvert.DeserializeObject<Data>(dataJson).Type);
    }
}

It throws the following error:

Run-time exception (line 27): Error converting value 9223372036854775808 to type 'Type'. Path 'Type', line 1, position 27.

Stack Trace:

[System.ArgumentException: The value passed in must be an enum base or an underlying type for an enum, such as an Int32.
Parameter name: value]
   at System.Enum.ToObject(Type enumType, Object value)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.EnsureType(JsonReader reader, Object value, CultureInfo culture, JsonContract contract, Type targetType)

[Newtonsoft.Json.JsonSerializationException: Error converting value 9223372036854775808 to type 'Type'. Path 'Type', line 1, position 27.]
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.EnsureType(JsonReader reader, Object value, CultureInfo culture, JsonContract contract, Type targetType)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue(JsonProperty property, JsonConverter propertyConverter, JsonContainerContract containerContract, JsonProperty containerProperty, JsonReader reader, Object target)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)
   at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType)
   at Newtonsoft.Json.JsonConvert.DeserializeObject(String value, Type type, JsonSerializerSettings settings)
   at Newtonsoft.Json.JsonConvert.DeserializeObject[T](String value, JsonSerializerSettings settings)
   at Newtonsoft.Json.JsonConvert.DeserializeObject[T](String value)
   at Program.Main() :line 27

And here is a link .NET Fiddle so you can check it by yourself.

Everything works fine for lower values (Type.Alpha and Type.Beta), but crashes for larger values (Type.Gamma and Type.Omega).

Cyberboss commented 4 years ago

I also run into this when trying to de-serialize ~0UL for a [Flags] enum.

Cyberboss commented 4 years ago

Note that ~0U works. https://github.com/tgstation/tgstation-server/commit/db341d43b3dab74fe3681f5172ca9bfeaafa6b6d#diff-09f06ec4584665cf89bb77b97f5ccfb9R36-R39

dbc2 commented 4 years ago

The basic problem seems to be that, when the numeric value of an enum is larger than long.MaxValue, JsonReader.Value is a BigInteger and the enum deserialization code isn't handling this.

For a fixed version of StringEnumConverter that handles ulong values larger than long.MaxValue see JSON.NET Unable to deserialize ulong flag type enum on stack overflow. Of course that converter handles serialization and deserialization of enums as strings. If you only want numeric serialization you could make a custom JsonConverter for all enum types whose underlying type is ulong that checks for and handles BigInteger values in a similar way.