【问题标题】:JSON.Net serializing Enums to strings in dictionaries by default - how to make it serialize to int?JSON.Net默认将枚举序列化为字典中的字符串-如何使其序列化为int?
【发布时间】:2022-04-16 05:06:42
【问题描述】:

为什么我的序列化 JSON 最终是

{"Gender":1,"Dictionary":{"Male":100,"Female":200}}

即为什么枚举序列化为它们的值,但是当它们形成字典时,它们被转换为它们的键?

如何使它们成为字典中的整数,为什么这不是默认行为?

我希望得到以下输出

{"Gender":1,"Dictionary":{"0":100,"1":200}}

我的代码:

    public void foo()
    {
        var testClass = new TestClass();
        testClass.Gender = Gender.Female;
        testClass.Dictionary.Add(Gender.Male, 100);
        testClass.Dictionary.Add(Gender.Female, 200);

        var serializeObject = JsonConvert.SerializeObject(testClass);

        // serializeObject == {"Gender":1,"Dictionary":{"Male":100,"Female":200}}
    }

    public enum Gender
    {
        Male = 0,
        Female = 1
    }

    public class TestClass
    {
        public Gender Gender { get; set; }
        public IDictionary<Gender, int> Dictionary { get; set; }

        public TestClass()
        {
            this.Dictionary = new Dictionary<Gender, int>();
        }
    }
}

【问题讨论】:

标签: c# json.net


【解决方案1】:

Gender枚举作为属性值时被序列化为其值,而作为字典键时被序列化为其字符串表示的原因如下:

  • 当用作属性值时,JSON.NET 序列化程序首先写入属性名称,然后写入属性值。对于您发布的示例,JSON.NET 将“性别”写入属性名称(注意它写入一个字符串),然后尝试解析属性的值。该属性的值是枚举类型,JSON.NET 将其处理为Int32,并写入枚举的数字表示

  • 在序列化字典时,键被写为属性名称,因此 JSON.NET 序列化程序会写入枚举的字符串表示形式。如果您在字典中切换键和值的类型(Dictionary&lt;int, Gender&gt; 而不是Dictionary&lt;Gender, int&gt;,您将验证枚举将使用其Int32 表示进行序列化。

要通过您发布的示例实现您想要的效果,您需要为 Dictionary 属性编写自定义JsonConverter。像这样的:

public class DictionaryConverter : JsonConverter
{

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var dictionary = (Dictionary<Gender, int>) value;

        writer.WriteStartObject();

        foreach (KeyValuePair<Gender, int> pair in dictionary)
        {
            writer.WritePropertyName(((int)pair.Key).ToString());
            writer.WriteValue(pair.Value);
        }

        writer.WriteEndObject();
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var jObject = JObject.Load(reader);

        var maleValue = int.Parse(jObject[((int) Gender.Male).ToString()].ToString());
        var femaleValue = int.Parse(jObject[((int)Gender.Female).ToString()].ToString());

        (existingValue as Dictionary<Gender, int>).Add(Gender.Male, maleValue);
        (existingValue as Dictionary<Gender, int>).Add(Gender.Female, femaleValue);

        return existingValue;
    }

    public override bool CanConvert(Type objectType)
    {
        return typeof (IDictionary<Gender, int>) == objectType;
    }
}

并装饰TestClass中的属性:

public class TestClass
{
    public Gender Gender { get; set; }
    [JsonConverter(typeof(DictionaryConverter))]
    public IDictionary<Gender, int> Dictionary { get; set; }

    public TestClass()
    {
        this.Dictionary = new Dictionary<Gender, int>();
    }
}

调用以下行进行序列化时:

var serializeObject = JsonConvert.SerializeObject(testClass);

你会得到想要的输出:

{"Gender":1,"Dictionary":{"0":100,"1":200}}

【讨论】:

  • 我自己刚刚经历过,这就是我处理它的方式。
【解决方案2】:

我经常发现自己面临这个问题,所以我做了一个 JsonConverter,它可以处理任何类型的字典,其中 Enum 类型作为键:

public class DictionaryWithEnumKeyConverter<T, U> : JsonConverter where T : System.Enum
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var dictionary = (Dictionary<T, U>)value;

        writer.WriteStartObject();

        foreach (KeyValuePair<T, U> pair in dictionary)
        {
            writer.WritePropertyName(Convert.ToInt32(pair.Key).ToString());
            serializer.Serialize(writer, pair.Value);
        }

        writer.WriteEndObject();
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var result = new Dictionary<T, U>();
        var jObject = JObject.Load(reader);

        foreach (var x in jObject)
        {
            T key = (T) (object) int.Parse(x.Key); // A bit of boxing here but hey
            U value = (U) x.Value.ToObject(typeof(U));
            result.Add(key, value);
        }

        return result;
    }

    public override bool CanConvert(Type objectType)
    {
        return typeof(IDictionary<T, U>) == objectType;
    }
}

注意:这将无法处理 Dictionnary&lt;Enum, Dictionnary&lt;Enum, T&gt;

【讨论】:

    【解决方案3】:

    Ilija Dimov 的回答涵盖了它发生的原因,但建议的转换器仅适用于这种特定情况。

    这是一个可重用的转换器,它将枚举键格式化为它们的值,对于任何Dictionary&lt;,&gt;/IDictionary&lt;,&gt; 字段中的任何枚举键:

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;
    using System.Linq;
    using Newtonsoft.Json;
    
    /// <summary>A Json.NET converter which formats enum dictionary keys as their underlying value instead of their name.</summary>
    public class DictionaryNumericEnumKeysConverter : JsonConverter
    {
        public override bool CanRead => false; // the default converter handles numeric keys fine
        public override bool CanWrite => true;
    
        public override bool CanConvert(Type objectType)
        {
            return this.TryGetEnumType(objectType, out _);
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
        {
            throw new NotSupportedException($"Reading isn't implemented by the {nameof(DictionaryNumericEnumKeysConverter)} converter."); // shouldn't be called since we set CanRead to false
        }
    
        public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
        {
            // handle null
            if (value is null)
            {
                writer.WriteNull();
                return;
            }
    
            // get dictionary & key type
            if (value is not IDictionary dictionary || !this.TryGetEnumType(value.GetType(), out Type? enumType))
                throw new InvalidOperationException($"Can't parse value type '{value.GetType().FullName}' as a supported dictionary type."); // shouldn't be possible since we check in CanConvert
            Type enumValueType = Enum.GetUnderlyingType(enumType);
    
            // serialize
            writer.WriteStartObject();
            foreach (DictionaryEntry pair in dictionary)
            {
                writer.WritePropertyName(Convert.ChangeType(pair.Key, enumValueType).ToString()!);
                serializer.Serialize(writer, pair.Value);
            }
            writer.WriteEndObject();
        }
    
        /// <summary>Get the enum type for a dictionary's keys, if applicable.</summary>
        /// <param name="objectType">The possible dictionary type.</param>
        /// <param name="keyType">The dictionary key type.</param>
        /// <returns>Returns whether the <paramref name="objectType"/> is a supported dictionary and the <paramref name="keyType"/> was extracted.</returns>
        private bool TryGetEnumType(Type objectType, [NotNullWhen(true)] out Type? keyType)
        {
            // ignore if type can't be dictionary
            if (!objectType.IsGenericType || objectType.IsValueType)
            {
                keyType = null;
                return false;
            }
    
            // ignore if not a supported dictionary
            {
                Type genericType = objectType.GetGenericTypeDefinition();
                if (genericType != typeof(IDictionary<,>) && genericType != typeof(Dictionary<,>))
                {
                    keyType = null;
                    return false;
                }
            }
    
            // extract key type
            keyType = objectType.GetGenericArguments().First();
            if (!keyType.IsEnum)
                keyType = null;
    
            return keyType != null;
        }
    }
    

    您可以在特定字段上启用它:

    [JsonConverter(typeof(DictionaryNumericEnumKeysConverter))]
    public IDictionary<Gender, int> Dictionary { get; set; }
    

    或者为所有带有枚举键的字典启用它:

    JsonConvert.DefaultSettings = () => new JsonSerializerSettings
    {
        Converters = new List<JsonConverter>
        {
            new DictionaryNumericEnumKeysConverter()
        }
    };
    

    【讨论】:

    • (次要问题:一个问题的答案没有按保证的顺序出现,所以提到“最佳答案”并没有多大意义。在这个页面上,对我来说,你的答案是最重要的!我进行了编辑以指向我认为您的意思的答案。)
    猜你喜欢
    • 2013-09-09
    • 2012-05-10
    • 2017-06-16
    • 2012-02-27
    • 2013-12-04
    • 2020-07-31
    • 1970-01-01
    • 2017-04-07
    • 1970-01-01
    相关资源
    最近更新 更多