繁体   English   中英

如何使用 Json.Net 序列化/反序列化带有自定义键的字典?

[英]How can I serialize/deserialize a dictionary with custom keys using Json.Net?

我有以下 class,我将其用作字典中的键:

    public class MyClass
    {
        private readonly string _property;

        public MyClass(string property)
        {
            _property = property;
        }

        public string Property
        {
            get { return _property; }
        }

        public override bool Equals(object obj)
        {
            MyClass other = obj as MyClass;
            if (other == null) return false;
            return _property == other._property;
        }

        public override int GetHashCode()
        {
            return _property.GetHashCode();
        }
    }

我正在运行的测试在这里:

    [Test]
    public void SerializeDictionaryWithCustomKeys()
    {
        IDictionary<MyClass, object> expected = new Dictionary<MyClass, object>();
        expected.Add(new MyClass("sth"), 5.2);
        JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All };
        string output = JsonConvert.SerializeObject(expected, Formatting.Indented, jsonSerializerSettings);
        var actual = JsonConvert.DeserializeObject<IDictionary<MyClass, object>>(output, jsonSerializerSettings);
        CollectionAssert.AreEqual(expected, actual);
    }

测试失败,因为 Json.Net 似乎在字典键上使用了ToString()方法,而不是正确地序列化它们。 上面测试的结果 json 是:

{
  "$type": "System.Collections.Generic.Dictionary`2[[RiskAnalytics.UnitTests.API.TestMarketContainerSerialisation+MyClass, RiskAnalytics.UnitTests],[System.Object, mscorlib]], mscorlib",
  "RiskAnalytics.UnitTests.API.TestMarketContainerSerialisation+MyClass": 5.2
}

这显然是错误的。 我怎样才能让它工作?

这应该可以解决问题:

序列化:

JsonConvert.SerializeObject(expected.ToArray(), Formatting.Indented, jsonSerializerSettings);

通过调用expected.ToArray()您正在序列化KeyValuePair<MyClass, object>对象的数组而不是字典。

反序列化:

JsonConvert.DeserializeObject<KeyValuePair<IDataKey, object>[]>(output, jsonSerializerSettings).ToDictionary(kv => kv.Key, kv => kv.Value);

在这里反序列化数组,然后使用.ToDictionary(...)调用检索字典。

我不确定输出是否符合您的期望,但它肯定通过了平等断言。

Grx70 的回答很好 - 只是在这里添加一个替代解决方案。 我在一个 Web API 项目中遇到了这个问题,我没有调用SerializeObject而是允许序列化自动发生。

这个基于Brian Rogers 对类似问题的回答的自定义JsonConverter对我JsonConverter

public class DeepDictionaryConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return (typeof(IDictionary).IsAssignableFrom(objectType) ||
                TypeImplementsGenericInterface(objectType, typeof(IDictionary<,>)));
    }

    private static bool TypeImplementsGenericInterface(Type concreteType, Type interfaceType)
    {
        return concreteType.GetInterfaces()
               .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == interfaceType);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        Type type = value.GetType();
        IEnumerable keys = (IEnumerable)type.GetProperty("Keys").GetValue(value, null);
        IEnumerable values = (IEnumerable)type.GetProperty("Values").GetValue(value, null);
        IEnumerator valueEnumerator = values.GetEnumerator();

        writer.WriteStartArray();
        foreach (object key in keys)
        {
            valueEnumerator.MoveNext();

            writer.WriteStartArray();
            serializer.Serialize(writer, key);
            serializer.Serialize(writer, valueEnumerator.Current);
            writer.WriteEndArray();
        }
        writer.WriteEndArray();
    }

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

就我而言,我在MyCustomType具有NameId等属性的类上序列化Dictionary<MyCustomType, int>属性。 这是结果:

...
"dictionaryProp": [
    [
      {
        "name": "MyCustomTypeInstance1.Name",
        "description": null,
        "id": null
      },
      3
    ],
    [
      {
        "name": "MyCustomTypeInstance2.Name",
        "description": null,
        "id": null
      },
      2
    ]
]
...

使用自定义 JsonConverter 的更简单、完整的解决方案

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;

public class CustomDictionaryConverter<TKey, TValue> : JsonConverter
{
    public override bool CanConvert(Type objectType) => objectType == typeof(Dictionary<TKey, TValue>);

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        => serializer.Serialize(writer, ((Dictionary<TKey, TValue>)value).ToList());

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        => serializer.Deserialize<KeyValuePair<TKey, TValue>[]>(reader).ToDictionary(kv => kv.Key, kv => kv.Value);
}

用法:

[JsonConverter(typeof(CustomDictionaryConverter<KeyType, ValueType>))]
public Dictionary<KeyType, ValueType> MyDictionary;

由于您的 class 可以轻松地序列化和反序列化为纯字符串,因此可以使用自定义 Json 转换器来完成,同时保持 Json 的 object 结构

为此,我编写了一个 JsonConverter 来转换 object 样式的任何字典,而无需使用 arrays 或为自定义键类型键入 arguments: Json.NET 转换器用于 object 样式的自定义键字典

要点是手动检查键值对,并强制对源自 Json object 属性的键类型进行序列化。 我可以制作的最简约的工作示例:

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
    // Aquire reflection info & get key-value-pairs:
    Type type = value.GetType();
    bool isStringKey = type.GetGenericArguments()[0] == typeof(string);
    IEnumerable keys = (IEnumerable)type.GetProperty("Keys").GetValue(value, null);
    IEnumerable values = (IEnumerable)type.GetProperty("Values").GetValue(value, null);
    IEnumerator valueEnumerator = values.GetEnumerator();

    // Write each key-value-pair:
    StringBuilder sb = new StringBuilder();
    using (StringWriter tempWriter = new StringWriter(sb))
    {
        writer.WriteStartObject();
        foreach (object key in keys)
        {
            valueEnumerator.MoveNext();

            // convert key, force serialization of non-string keys
            string keyStr = null;
            if (isStringKey)
            {
                // Key is not a custom type and can be used directly
                keyStr = (string)key;
            }
            else
            {
                sb.Clear();
                serializer.Serialize(tempWriter, key);
                keyStr = sb.ToString();
                // Serialization can wrap the string with literals
                if (keyStr[0] == '\"' && keyStr[str.Length-1] == '\"')
                    keyStr = keyStr.Substring(1, keyStr.Length - 1);
                // TO-DO: Validate key resolves to single string, no complex structure
            }
            writer.WritePropertyName(keyStr);

            // default serialize value
            serializer.Serialize(writer, valueEnumerator.Current);
        }
        writer.WriteEndObject();
    }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
    // Aquire reflection info & create resulting dictionary:
    Type[] dictionaryTypes = objectType.GetGenericArguments();
    bool isStringKey = dictionaryTypes[0] == typeof(string);
    IDictionary res = Activator.CreateInstance(objectType) as IDictionary;

    // Read each key-value-pair:
    object key = null;
    object value = null;

    while (reader.Read())
    {
        if (reader.TokenType == JsonToken.EndObject)
            break;

        if (reader.TokenType == JsonToken.PropertyName)
        {
            key = isStringKey ? reader.Value : serializer.Deserialize(reader, dictionaryTypes[0]);
        }
        else
        {
            value = serializer.Deserialize(reader, dictionaryTypes[1]);

            res.Add(key, value);
            key = null;
            value = null;
        }
    }

    return res;
}

使用这样的转换器,JSON 对象可以像您期望的那样直接用作字典。 换句话说,现在可以这样做:

{
  MyDict: {
    "Key1": "Value1",
    "Key2": "Value2"
    [...]
  }
}

而不是这个:

{
  MyDict: [
    ["Key1", "Value1"],
    ["Key2", "Value2"]
    [...]
  ]
}

有关详细信息,请参阅存储库。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM