无法使用www.example.com序列化具有复杂键的字典Json.net

wmvff8tz  于 2023-01-10  发布在  .NET
关注(0)|答案(7)|浏览(113)

我有一个字典,其键为自定义.net类型。我尝试使用www.example.com将此字典序列化为JSONJSON.net,但在序列化过程中无法将键转换为正确的值。

class ListBaseClass
{
    public String testA;
    public String testB;
}
-----
var details = new Dictionary<ListBaseClass, string>();
details.Add(new ListBaseClass { testA = "Hello", testB = "World" }, "Normal");
var results = Newtonsoft.Json.JsonConvert.SerializeObject(details);
var data = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<ListBaseClass, string>> results);

这给予我--〉“{\“JSonSerialization.ListBaseClass":\“正常"}”
但是,如果我有我的自定义类型作为字典中的值,它工作得很好

var details = new Dictionary<string, ListBaseClass>();
  details.Add("Normal", new ListBaseClass { testA = "Hello", testB = "World" });
  var results = Newtonsoft.Json.JsonConvert.SerializeObject(details);
  var data = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, ListBaseClass>>(results);

这给予我--〉“{\“正常":{\“测试A":\“您好",\“测试B":\“世界"}}”
有人能建议我是否遇到了www.example.com的一些限制Json.net或我做错了什么吗?

tzcvj98z

tzcvj98z1#

你可能不想使用Gordon Bean给出的答案。这个解决方案是可行的,但是它提供了一个序列化的字符串作为输出。如果你使用JSON,这将给予你一个不太理想的结果,因为你真正想要的是一个对象的JSON表示,而不是字符串表示。
例如,假设您有一个将唯一网格点与字符串相关联的数据结构:

class Point
{
    public int x { get; set; }
    public int y { get; set; }
}

public Dictionary<Point,string> Locations { get; set; };

使用TypeConverter重写,您将在序列化此对象时获得它的字符串表示形式。

"Locations": {
  "4,3": "foo",
  "3,4": "bar"
},

但我们真正想要的是:

"Locations": {
  { "x": 4, "y": 3 }: "foo",
  { "x": 3, "y": 4 }: "bar"
},

重写TypeConverter以序列化/反序列化类存在几个问题。
首先,这不是JSON,您可能需要编写额外的自定义逻辑来处理序列化和反序列化。(例如,可能在客户端层编写Javascript?)
第二,使用该对象的任何其他地方现在都将抛出该字符串,而以前它将正确地序列化为一个对象:

"GridCenterPoint": { "x": 0, "y": 0 },

现在序列化为:

"GridCenterPoint": "0,0",

您可以稍微控制TypeConverter的格式设置,但无法摆脱它呈现为字符串而不是对象的事实。
这个问题并不是序列化器的问题,因为Json .NET可以不间断地处理复杂的对象,而是字典键的处理方式的问题。如果你尝试使用示例对象,序列化一个List或者甚至是一个Hashset,你会注意到生成正确的JSON并没有问题。这给了我们一个更简单的方法来解决这个问题。
理想情况下,我们只想告诉Json .NET将键序列化为任何对象类型,而不是将其强制为字符串。由于这似乎不是一个选项,因此另一种方法是为Json .NET提供一些它可以使用的东西:a x 1m 0n 1x。
如果您将KeyValuePair列表提供给Json .NET的序列化程序,您将得到您所期望的结果。例如,下面是一个您可以实现的简单得多的 Package 器:

private Dictionary<Point, string> _Locations;
    public List<KeyValuePair<Point, string>> SerializedLocations
    {
        get { return _Locations.ToList(); }
        set { _Locations= value.ToDictionary(x => x.Key, x => x.Value); }
    }

从@bmw15评论更新:
您可以将Dictionary prop设置为公共属性,向其添加[JsonIgnore],并使用[JsonProperty]属性将KeyValuePair列表设置为私有属性
这个技巧很有效,因为kvp中的键不会被强制转换成字符串格式。你会问为什么是字符串格式?这把我难住了。Dictionary对象实现了IEnumerable<KeyValuePair<TKey, TValue>>接口,所以以与kvp列表相同的方式序列化它应该没有任何问题。因为这就是字典的本质(JamesNewton?)在编写Newtonsoft字典序列化程序时做出了一个决定,即复杂的键太乱,无法处理。可能有一些角落的情况下,我没有考虑到,使这是一个更棘手的问题。
这是一个更好的解决方案,因为它生成实际的JSON对象,技术上更简单,并且不会产生任何替换序列化程序所带来的副作用。

jtoj6r0c

jtoj6r0c2#

Serialization Guide状态(参见章节:字典和哈希表谢谢你@Shashwat的链接):
序列化字典时,字典的键将转换为字符串并用作JSON对象属性名。通过重写键类型的ToString()或实现TypeConverter,可以自定义为键编写的字符串。TypeConverter还支持在反序列化字典时将自定义字符串再次转换回来。
我在Microsoft的“how-to”页面上找到了一个关于如何实现这种类型转换器的有用示例:

实际上,我需要扩展System.ComponentModel.TypeConverter并覆盖:

bool CanConvertFrom(ITypeDescriptorContext context, Type source);

object ConvertFrom(ITypeDescriptorContext context,
                   System.Globalization.CultureInfo culture, object value);

object ConvertTo(ITypeDescriptorContext context, 
                 System.Globalization.CultureInfo culture, 
                 object value, Type destinationType);

还必须将属性[TypeConverter(typeof(MyClassConverter))]添加到MyClass类声明中。
有了这些,我就能够 * 自动 * 序列化和反序列化字典了。

qxsslcnc

qxsslcnc3#

另一种方法是使用自定义ContractResolver并设置OverrideCreator。

public class DictionaryAsArrayResolver : DefaultContractResolver
{
    public override JsonContract CreateContract(Type objectType)
    {
        if (IsDictionary(objectType))
        {
            JsonArrayContract contract = base.CreateArrayContract(objectType);
            contract.OverrideCreator = (args) => CreateInstance(objectType);
            return contract;
        }

        return base.CreateContract(objectType);
    }

    internal static bool IsDictionary(Type objectType)
    {
        if (objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(IDictionary<,>))
        {
            return true;
        }

        if (objectType.GetInterface(typeof(IDictionary<,>).Name) != null)
        {
            return true;
        }

        return false;
    }

    private object CreateInstance(Type objectType)
    {
        Type dictionaryType = typeof(Dictionary<,>).MakeGenericType(objectType.GetGenericArguments());
        return Activator.CreateInstance(dictionaryType);
    }
}

用法:

JsonSerializer jsonSerializer = new JsonSerializer();
jsonSerializer.ContractResolver = new DictionaryAsArrayResolver();
agyaoht7

agyaoht74#

在@roger-hill answer之后,我想出了一个轻量级的解决方案来达到同样的效果:

[JsonArray]
    public class MyDictionary<K, V> : Dictionary<K, V>
    {
    }

通过这种方式,每个MyDictionary对象都被序列化为键/值对的数组,对于复杂的键类型也能正常工作:

[{
    "Key": ...,
    "Value": ...
}, ...]
dhxwm5r4

dhxwm5r45#

灵感来自gson enableComplexMapKeySerialization及其外观/工作原理:

public class DictionaryAsArrayJsonConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var dictionary = (IDictionary)value;

        writer.WriteStartArray();

        var en = dictionary.GetEnumerator();
        while (en.MoveNext())
        {
            writer.WriteStartArray();
            serializer.Serialize(writer, en.Key);
            serializer.Serialize(writer, en.Value);
            writer.WriteEndArray();
        }
        
        writer.WriteEndArray();
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (!CanConvert(objectType))
            throw new Exception(string.Format("This converter is not for {0}.", objectType));

        Type keyType = null;
        Type valueType = null;
        IDictionary result;

        if (objectType.IsGenericType)
        {
            keyType = objectType.GetGenericArguments()[0];
            valueType = objectType.GetGenericArguments()[1];
            var dictionaryType = typeof(Dictionary<,>).MakeGenericType(keyType, valueType);
            result = (IDictionary)Activator.CreateInstance(dictionaryType);
        }
        else
        {
            result = (IDictionary)Activator.CreateInstance(objectType);
        }

        if (reader.TokenType == JsonToken.Null)
            return null;

        int depth = reader.Depth;
        while (reader.Read())
        {
            if (reader.TokenType == JsonToken.StartArray)
            {
            }
            else if (reader.TokenType == JsonToken.EndArray)
            {
                if (reader.Depth == depth)
                    return result;
            }
            else
            {
                object key = serializer.Deserialize(reader, keyType);
                reader.Read();
                object value = serializer.Deserialize(reader, valueType);
                result.Add(key, value);
            }
        }

        return result;
    }

    public override bool CanConvert(Type objectType)
    {
        return typeof(IDictionary).IsAssignableFrom(objectType);
    }

}

可以创建与Tal Aloni代码相同的json,但作为JsonConverter而不是契约。更灵活,因为它可以使用JsonConverterAttribute在选定属性上使用,也可以使用JsonSerializerSettings.Converters.Add(...)在所有属性上使用

xpszyzbs

xpszyzbs6#

基于@roger-hill富有洞察力的回答,我创建了下面的JsonConverter,它将把一个IDictionary对象转换成KeyValuePair对象的List
github link

public class ListDictionaryConverter : JsonConverter
{
    private static (Type kvp, Type list, Type enumerable, Type[] args) GetTypes(Type objectType)
    {
        var args = objectType.GenericTypeArguments;
        var kvpType = typeof(KeyValuePair<,>).MakeGenericType(args);
        var listType = typeof(List<>).MakeGenericType(kvpType);
        var enumerableType = typeof(IEnumerable<>).MakeGenericType(kvpType);

        return (kvpType, listType, enumerableType, args);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var (kvpType, listType, _, args) = GetTypes(value.GetType());
        
        var keys = ((IDictionary)value).Keys.GetEnumerator();
        var values = ((IDictionary)value).Values.GetEnumerator();
        var cl = listType.GetConstructor(Array.Empty<Type>());
        var ckvp = kvpType.GetConstructor(args);
        
        var list = (IList)cl!.Invoke(Array.Empty<object>());
        while (keys.MoveNext() && values.MoveNext())
        {
            list.Add(ckvp!.Invoke(new []{keys.Current, values.Current}));
        }
        
        serializer.Serialize(writer, list);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var (_, listType, enumerableType, args) = GetTypes(objectType);
        
        var list = ((IList)(serializer.Deserialize(reader, listType)));

        var ci = objectType.GetConstructor(new[] {enumerableType});
        if (ci == null)
        {
            ci = typeof(Dictionary<,>).MakeGenericType(args).GetConstructor(new[] {enumerableType});
        }
        
        var dict = (IDictionary) ci!.Invoke(new object[]{ list });

        return dict;
    }

    public override bool CanConvert(Type objectType)
    {
        if (!objectType.IsGenericType) return objectType.IsAssignableTo(typeof(IDictionary));
        
        var args = objectType.GenericTypeArguments;
        return args.Length == 2 && objectType.IsAssignableTo(typeof(IDictionary<,>).MakeGenericType(args));
    }
}

我做了一些测试,这段代码在测试中运行良好......但我可能漏掉了一两个边缘情况。

sdnqo3pr

sdnqo3pr7#

一切都更容易

var details = new Dictionary<string, ListBaseClass>();
details.Add("Normal", new ListBaseClass { testA = "Hello", testB = "World" });
var results = Newtonsoft.Json.JsonConvert.SerializeObject(details.ToList());
var data = 
Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<ListBaseClass, string>> results);

样品

class Program
{
    static void Main(string[] args)
    {
        var  testDictionary = new Dictionary<TestKey,TestValue>()
        {
            {
                new TestKey()
                {
                    TestKey1 = "1",
                    TestKey2 = "2",
                    TestKey5 = 5
                },
                new TestValue()
                {
                    TestValue1 = "Value",
                    TestValue5 = 96
                }
            }
        };

        var json = JsonConvert.SerializeObject(testDictionary);
        Console.WriteLine("=== Dictionary<TestKey,TestValue> ==");
        Console.WriteLine(json);
        // result: {"ConsoleApp2.TestKey":{"TestValue1":"Value","TestValue5":96}}

        json = JsonConvert.SerializeObject(testDictionary.ToList());
        Console.WriteLine("=== List<KeyValuePair<TestKey, TestValue>> ==");
        Console.WriteLine(json);
        // result: [{"Key":{"TestKey1":"1","TestKey2":"2","TestKey5":5},"Value":{"TestValue1":"Value","TestValue5":96}}]

        Console.ReadLine();

    }
}

class TestKey
{
    public string TestKey1 { get; set; }

    public string TestKey2 { get; set; }

    public int TestKey5 { get; set; }
}

class TestValue 
{
    public string TestValue1 { get; set; }

    public int TestValue5 { get; set; }
}

相关问题