JSON.NET无法反序列化ulong标志类型枚举 [英] JSON.NET Unable to deserialize ulong flag type enum

查看:62
本文介绍了JSON.NET无法反序列化ulong标志类型枚举的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个枚举的标志类型,我希望它不以字符串而是以数字序列化. 序列化没有问题,但json.net无法反序列化.

I have a flag type of enum and I would like it to be serialized not as string but as number. There is no problem about serializing but json.net unable to deserialize it.

public class ForceNumericFlagEnumConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        if (!(Nullable.GetUnderlyingType(objectType) ?? objectType).IsEnum)
            return false;
        return HasFlagsAttribute(objectType);
    }

    public override bool CanRead { get { return false; } }

    public override bool CanWrite { get { return false; } }

    static bool HasFlagsAttribute(Type objectType) 
    { 
        return Attribute.IsDefined(Nullable.GetUnderlyingType(objectType) ?? objectType, typeof(System.FlagsAttribute));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

public class Program
{
    public static void Main()
    {
        try
        {
            Test(UserCan.ChangeConf | UserCan.ForceOther);
            Test(UserCan.DoNothing);
            Test(UserCan.ForceOther);
            Test(UserCan.DoEverything);
        }
        catch (Exception ex)
        {
            Console.WriteLine("Uncaught exception:");
            Console.WriteLine(ex);
            throw;
        }
    }

    static void Test(UserCan ability)
    {
        JsonConvert.DefaultSettings = () => GetGlobalJsonSettings();

        var settings = GetNEWJsonSettings();

        string jsonAbility = JsonConvert.SerializeObject(ability, settings);
        Console.WriteLine("\nJSON serialization for {0} \"{1}\":", ability.GetType(), ability);
        Console.WriteLine(jsonAbility);

        ulong val;
        Assert.IsTrue(ulong.TryParse(jsonAbility, out val));

        var result1 = JsonConvert.DeserializeObject<UserCan?>(jsonAbility, settings);
        var result2 = JsonConvert.DeserializeObject<UserCan>(jsonAbility, settings);

        Assert.IsTrue(result1 == ability);
        Assert.IsTrue(result2 == ability);

        Assert.AreEqual("\"someValue\"", JsonConvert.SerializeObject(NonFlagEnum.SomeValue, settings));
    }

    public enum NonFlagEnum
    {
        SomeValue,
    }

    [Flags]
    public enum UserCan : ulong
    {
        DoNothing = 0,
        ChangeConf =               1 << 0,
        MonitorOthers =              1 << 1,
        ForceOther =                1 << 2,
        EditRemoveOthers =           1 << 3,

        DoEverything = unchecked((ulong)~0),
    }

    public static JsonSerializerSettings GetGlobalJsonSettings()
    {       
        JsonSerializerSettings settings = new JsonSerializerSettings
        {
            Converters = { new StringEnumConverter{ CamelCaseText = true } },
        };
        return settings;
    }

    public static JsonSerializerSettings GetNEWJsonSettings()
    {
        JsonSerializerSettings settings = new JsonSerializerSettings
        {
            DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate,
            NullValueHandling = NullValueHandling.Ignore,
            ObjectCreationHandling = ObjectCreationHandling.Replace, 
            TypeNameHandling = TypeNameHandling.Auto,
            Converters = { new ForceNumericFlagEnumConverter() },
        };
        return settings;
    }
}

您可以在此处看到完整的代码来创建错误

You can see full code to create error here

https://dotnetfiddle.net/y0GnNf

[Newtonsoft.Json.JsonSerializationException:将值18446744073709551615转换为"Program + UserCan"时出错.路径",第1行,位置20.]

[Newtonsoft.Json.JsonSerializationException: Error converting value 18446744073709551615 to type 'Program+UserCan'. Path '', line 1, position 20.]

推荐答案

您在Json.NET中遇到了带有ulong类型枚举的已知错误:

You have encountered a known bug in Json.NET with ulong-type enumerations: Failure to deserialize ulong enum #2301

ulong枚举值的序列化效果很好,
但是反序列化无法在较大的值上使用.

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

作为一种解决方法,如果您不是使用Json.NET的StringEnumConverter ,则可以添加以下转换器,该转换器可以正确处理ulong枚举的非常大的值:

As a workaround, if you are not using Json.NET's StringEnumConverter, you can add the following converter that correctly handles very large values for ulong enums:

public class FixedUlongEnumConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        var enumType = Nullable.GetUnderlyingType(objectType) ?? objectType;
        if (!enumType.IsEnum)
            return false;
        return Enum.GetUnderlyingType(enumType) == typeof(ulong);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var nullableUnderlying = Nullable.GetUnderlyingType(objectType);
        var enumType = nullableUnderlying ?? objectType;
        var isNullable = nullableUnderlying != null;
        switch (reader.MoveToContentAndAssert().TokenType)
        {
            case JsonToken.Null:
                if (!isNullable)
                    throw new JsonSerializationException(string.Format("Null value for {0}", objectType));
                return null;

            case JsonToken.Integer:
                if (reader.ValueType == typeof(System.Numerics.BigInteger))
                {
                    var bigInteger = (System.Numerics.BigInteger)reader.Value;
                    if (bigInteger >= ulong.MinValue && bigInteger <= ulong.MaxValue)
                    {
                        return Enum.ToObject(enumType, checked((ulong)bigInteger));
                    }
                    else
                    {
                        throw new JsonSerializationException(string.Format("Value {0} is too large for enum {1}", bigInteger, enumType));
                    }
                }
                else
                {
                    return Enum.ToObject(enumType, reader.Value);
                }

            default:
                throw new JsonSerializationException(string.Format("Unexpected token {0}", reader.TokenType));          
        }
    }

    public override bool CanWrite { get { return false; } }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

public static partial class JsonExtensions
{
    public static JsonReader MoveToContentAndAssert(this JsonReader reader)
    {
        if (reader == null)
            throw new ArgumentNullException();
        if (reader.TokenType == JsonToken.None)       // Skip past beginning of stream.
            reader.ReadAndAssert();
        while (reader.TokenType == JsonToken.Comment) // Skip past comments.
            reader.ReadAndAssert();
        return reader;
    }

    public static JsonReader ReadAndAssert(this JsonReader reader)
    {
        if (reader == null)
            throw new ArgumentNullException();
        if (!reader.Read())
            throw new JsonReaderException("Unexpected end of JSON stream.");
        return reader;
    }
}

演示小提琴#1 此处.

如果您使用Json.NET的StringEnumConverter ,但有时仍可能获得枚举的数值,请用此固定版本替换它:

And if you are using Json.NET's StringEnumConverter but might sometimes get numeric values for enums anyway, replace it with this fixed version:

public class FixedUlongStringEnumConverter : StringEnumConverter
{
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.MoveToContentAndAssert().TokenType == JsonToken.Integer && reader.ValueType == typeof(System.Numerics.BigInteger))
        {
            // Todo: throw an exception if !this.AllowIntegerValues
            // https://www.newtonsoft.com/json/help/html/P_Newtonsoft_Json_Converters_StringEnumConverter_AllowIntegerValues.htm
            var enumType = Nullable.GetUnderlyingType(objectType) ?? objectType;
            if (Enum.GetUnderlyingType(enumType) == typeof(ulong))
            {
                var bigInteger = (System.Numerics.BigInteger)reader.Value;
                if (bigInteger >= ulong.MinValue && bigInteger <= ulong.MaxValue)
                {
                    return Enum.ToObject(enumType, checked((ulong)bigInteger));
                }
            }
        }
        return base.ReadJson(reader, objectType, existingValue, serializer);
    }
}

public static partial class JsonExtensions
{
    public static JsonReader MoveToContentAndAssert(this JsonReader reader)
    {
        if (reader == null)
            throw new ArgumentNullException();
        if (reader.TokenType == JsonToken.None)       // Skip past beginning of stream.
            reader.ReadAndAssert();
        while (reader.TokenType == JsonToken.Comment) // Skip past comments.
            reader.ReadAndAssert();
        return reader;
    }

    public static JsonReader ReadAndAssert(this JsonReader reader)
    {
        if (reader == null)
            throw new ArgumentNullException();
        if (!reader.Read())
            throw new JsonReaderException("Unexpected end of JSON stream.");
        return reader;
    }
}

现在,您还需要将[Flags]枚举序列化,并且仅将[Flags]枚举序列化为整数.您可以通过进一步细分上述转换器,如下所示:

Now, you have an additional requirement to serialize [Flags] enums, and only [Flags] enums, as integers. You can do this by further subclassing the above converter as follows:

public class ForceNumericFlagEnumConverter : FixedUlongStringEnumConverter
{
    static bool HasFlagsAttribute(Type objectType) 
    { 
        return Attribute.IsDefined(Nullable.GetUnderlyingType(objectType) ?? objectType, typeof(System.FlagsAttribute));
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var enumType = value.GetType();
        if (HasFlagsAttribute(enumType))
        {
            var underlyingType = Enum.GetUnderlyingType(enumType);
            var underlyingValue = Convert.ChangeType(value, underlyingType);
            writer.WriteValue(underlyingValue);
        }
        else
        {
            base.WriteJson(writer, value, serializer);
        }
    }   
}

解决方法要求支持 BigInteger 在.NET Framework 4.0中引入.

The workaround requires support for BigInteger which was introduced in .NET Framework 4.0.

演示小提琴#2 此处.

这篇关于JSON.NET无法反序列化ulong标志类型枚举的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆