无法使用Json.net使用Complex键序列化Dictionary

Sha*_*pta 32 c# serialization json dictionary json.net

我有一个自定义.net类型的字典作为其关键.我正在尝试使用JSON.net将此字典序列化为JSON,但是它无法在序列化期间将键转换为适当的值.

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);
Run Code Online (Sandbox Code Playgroud)

这个给我 - >"{\"JSonSerialization.ListBaseClass \":\"Normal \"}"

但是,如果我将自定义类型作为字典中的值,则可以正常工作

  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);
Run Code Online (Sandbox Code Playgroud)

这个给我 - >"{\"普通\":{\"testA \":\"Hello \",\"testB \":\"World \"}}"

有人建议如果我遇到Json.net的某些限制或我做错了什么?

Gor*_*ean 26

序列化向导状态(参见:字典和哈希表;感谢你@Shashwat的链接):

序列化字典时,字典的键将转换为字符串并用作JSON对象属性名称.为密钥编写的字符串可以通过覆盖密钥类型的ToString()或通过实现TypeConverter来自定义.在反序列化字典时,TypeConverter还将支持再次转换自定义字符串.

我找到了一个有用的示例,说明如何在Microsoft的"操作方法"页面上实现这种类型的转换器:

基本上,我需要扩展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);
Run Code Online (Sandbox Code Playgroud)

还必须将该属性添加 [TypeConverter(typeof(MyClassConverter))]MyClass类声明中.

有了这些,我就能够自动序列化和反序列化字典.


Rog*_*ill 9

您可能不想使用Gordon Bean提出的答案。该解决方案有效,但是它提供了输出的序列化字符串。如果您使用的是JSON,这将给您带来不太理想的结果,因为您确实需要对象的JSON表示形式而不是字符串表示形式。

例如,假设您有一个将唯一的网格点与字符串相关联的数据结构:

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

public Dictionary<Point,string> Locations { get; set; };
Run Code Online (Sandbox Code Playgroud)

使用TypeConverter覆盖,在序列化该对象时,将获得该对象的字符串表示形式。

"Locations": {
  "4,3": "foo",
  "3,4": "bar"
},
Run Code Online (Sandbox Code Playgroud)

但是我们真正想要的是:

"Locations": {
  { "x": 4, "y": 3 }: "foo",
  { "x": 3, "y": 4 }: "bar"
},
Run Code Online (Sandbox Code Playgroud)

覆盖TypeConverter以序列化/反序列化类存在几个问题。

首先,它不是JSON,您可能必须编写其他自定义逻辑来处理序列化和反序列化。(例如,可能是客户端层中的Javascript?)

其次,现在使用该对象的其他任何地方都会喷出该字符串,之前该字符串已正确序列化为一个对象:

"GridCenterPoint": { "x": 0, "y": 0 },
Run Code Online (Sandbox Code Playgroud)

现在序列化为:

"GridCenterPoint": "0,0",
Run Code Online (Sandbox Code Playgroud)

您可以稍微控制TypeConverter的格式设置,但是不能逃避将其呈现为字符串而不是对象的事实。

这个问题不是序列化程序的问题,因为json.net在不丢失节奏的情况下咀嚼复杂的对象,因此处理字典键的方式存在问题。如果尝试使用示例对象并序列化List甚至是Hashset,则会注意到生成适当的JSON没有问题。这为我们提供了解决此问题的简单得多的方法。

理想情况下,我们只想告诉json.net将键序列化为任何对象类型,而不是将其强制为字符串。由于这似乎不是一种选择,所以另一种方法是给json.net它可以使用的东西:a List<KeyValuePair<T,K>>

如果将KeyValuePairs列表输入到json.net的序列化器中,则将得到您所期望的结果。例如,这是一个可以实现的简单得多的包装器:

    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); }
    }
Run Code Online (Sandbox Code Playgroud)

这个技巧行得通,因为kvp中的键不会强制转换为字符串格式。你问为什么呢?它使我不寒而栗。Dictionary对象实现了该IEnumerable<KeyValuePair<TKey, TValue>>接口,因此以与kvps列表相同的方式对其进行序列化应该没有任何问题,因为从本质上讲,这就是kvps列表的含义。编写Newtonsoft字典序列化程序时,有人(詹姆斯·牛顿(James Newton?))做出了一个决定,即复杂的键难以处理。我可能没有想到一些极端的情况,这使这个问题变得更加棘手。

这是一个更好的解决方案,因为它可以生成实际的JSON对象,在技术上更简单,并且不会由于替换序列化程序而产生任何副作用。