【问题标题】:Json.net: Serialisation/Deserialisation not working for ISerializable object that has circular referenceJson.net:序列化/反序列化不适用于具有循环引用的 ISerializable 对象
【发布时间】:2012-12-10 08:57:33
【问题描述】:

前段时间我报告了一个issue,我在 Json.net 4.5 R11 中对此进行了修复。

如果我的循环引用属性 Manager 为 NULL,则序列化和反序列化工作正常。

但是当循环引用属性 Manager 设置为 NON NULL 值时,它会在序列化字符串中被忽略,因此它会在反序列化中引发异常。

Json.net 问题库说问题出在您的代码中,但我无法弄清楚。有人可以帮我吗?

问题:

  1. 下面的代码有问题吗?
  2. 如果是,我应该怎么做才能解决问题?
  3. 如果不是,那么应该在 Json.net 代码中做些什么来解决这个问题?

更多更新: 这在当前使用二进制序列化的遗留应用程序中是必需的。由于变化巨大,使用 Json 序列化标签标记所有涉及到序列化的私有字段的工作量太大。由于 Json.net 可以对 ISerializable 对象进行序列化,因此我们希望这样做。如果没有循环引用对象,则此方法有效。

我的课程

[Serializable]
class Department : ISerializable
{
    public Employee Manager { get; set; }
    public string Name { get; set; }

    public Department() { }

    public Department( SerializationInfo info, StreamingContext context )
    {
        Manager = ( Employee )info.GetValue( "Manager", typeof( Employee ) ); //Manager's data not found since json string itself does not have Employee property
        Name = ( string )info.GetValue( "Name", typeof( string ) );
    }
    public void GetObjectData( SerializationInfo info, StreamingContext context )
    {
        info.AddValue( "Manager", Manager );
        info.AddValue( "Name", Name );
    }
}

[Serializable]
class Employee : ISerializable
{
    public Department Department { get; set; }
    public string Name { get; set; }

    public Employee() { }

    public Employee( SerializationInfo info, StreamingContext context )
    {
        Department = ( Department )info.GetValue( "Department", typeof( Department ) );
        Name = ( string )info.GetValue( "Name", typeof( string ) );
    }

    public void GetObjectData( SerializationInfo info, StreamingContext context )
    {
        info.AddValue( "Department", Department );
        info.AddValue( "Name", Name );
    }
}

我的测试代码:

JsonSerializerSettings jsonSS= new JsonSerializerSettings();
jsonSS.Formatting = Formatting.Indented;
jsonSS.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; //If there is referenced object then it is not shown in the json serialisation
//jsonSS.ReferenceLoopHandling = ReferenceLoopHandling.Serialize; //Throws stackoverflow error
jsonSS.PreserveReferencesHandling = PreserveReferencesHandling.All;


Department department = new Department();
department.Name = "Dept1";

Employee emp1 = new Employee { Name = "Emp1", Department = department };
department.Manager = null;

string json1 = JsonConvert.SerializeObject( emp1, jsonSS );
//json1 = 
//            {
//  "$id": "1",
//  "Department": {
//    "$id": "2",
//    "Manager": null,
//    "Name": "Dept1"
//  },
//  "Name": "Emp1"
//}

Employee empD1 = JsonConvert.DeserializeObject<Employee>( json1, jsonSS ); //Manager is set as null

department.Manager = emp1; //Non null manager is set
string json2 = JsonConvert.SerializeObject( emp1, jsonSS ); //SEE Manager property is missing

//  json2 =          {
//  "$id": "1",
//  "Department": {
//    "$id": "2",
//    "Name": "Dept1"
//  },
//  "Name": "Emp1"
//}

Employee empD2 = JsonConvert.DeserializeObject<Employee>( json2, jsonSS );  //Throws exception

【问题讨论】:

    标签: c# .net json serialization json.net


    【解决方案1】:

    基于khellang 的回答,它定位了使用ISerializable 接口实现时JSon.Net 无法处理循环引用的问题,您可以尝试强制JSon.Net 序列化程序忽略ISerializable实现,而不实际删除此实现。
    您应该能够通过使用 JsonObject 属性装饰您的类(DepartmentEmployee)来实现这一点。
    我没有测试这是否真的解决了你的问题。

    引用Serialization Guide(强调我的):

    实现 ISerializable 的类型被序列化为 JSON 对象。序列化时,仅使用从 ISerializable.GetObjectData 返回的值;类型上的成员被忽略。反序列化时,调用带有 SerializationInfo 和 StreamingContext 的构造函数,传递 JSON 对象的值。

    在不需要此行为的情况下,可以将 JsonObjectAttribute 放置在实现 ISerializable 的 .NET 类型上,以强制将其序列化为普通 JSON 对象。

    由于您的问题和 cmets 可以追溯到 2012 年,因此该解决方案当时可能不可用。即使使用 ISerializable,当前的 JSon.Net 实现也有可能处理循环引用。

    【讨论】:

      【解决方案2】:

      我会保持简短:)

      1. 看来是ISerializable 接口搞砸了。如果你删除它,一切都会完美运行。

      2. 你有一个REALLY good reason 用于在EmployeeDepartment 中实现ISerializable 接口吗?如果没有,只需将其删除。如果你这样做了,转到 3 ;)

      3. 我不知道 Newtonsoft.Json 内部原理,但不知何故,在实现 ISerializable 时它没有正确处理循环引用。您可能应该在GitHub 提出问题。

      【讨论】:

      • 1 & 2) 我有巨大的数据结构,它已经完全实现了带有私有公共属性的 ISerializable 接口。改变所有的结构需要很长时间才能解决。 Json.Net 支持 BTW ISerializable。
      • 3) 我已经针对问题stackoverflow.com/questions/13412359/… 提交了问题json.codeplex.com/workitem/23668,但他们说问题出在您的代码问题上,有人会提供帮助。我现在不知道该去哪里
      • 我已经用其他信息更新了我的问题。如果可以修改 json.net 代码来处理这种情况,那将有很大帮助
      • 由于您的已关闭,我创建了一个新问题,并附上了一个失败的测试here
      • 谢谢。如果您还可以添加反序列化行以防万一他们不会忘记进行测试,那就更好了。
      【解决方案3】:

      但是当循环引用属性Manager设置为NON NULL值 然后它在序列化字符串中被忽略,因此它抛出一个 反序列化异常。

      因为循环引用被忽略了。这就是 ReferenceLoopHandling.Ignore 的意义所在。

      PreserveReferencesHandling 不适用于 ISerializable,因为必须在父值之前创建子值。

      【讨论】:

      • 供参考LoopHandling.Serialize;它引发 stackoverflow 错误,@Khellang 为此创建了一个新问题,该问题已关闭。我正在考虑在创建时保留所有引用属性和子对象的映射,一旦创建对象,循环该映射并再次设置引用。但我发现它需要更多的努力,所以想把它留给专家。由于需要,我想做这个。我想知道这在二进制序列化中怎么可能?如果有可能,这里也应该有可能,因为对象创建机制是通过相同的构造函数发生的。
      【解决方案4】:

      看起来您确实不需要实现 Iserializable 接口。这种类结构很简单。我已经序列化了非常大的类,而不必走那么远。

      如果您仍然遇到循环引用问题,请尝试以下操作:

      1. 我相信 JSON.net 有一个格式化选项可以忽略循环引用

        JsonSerializerSettings jsSettings = new JsonSerializerSettings(); jsSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;

      2. 忽略违规属性

      3. 重组你的班级

      【讨论】:

      • 这远不是一个答案。他已经提到,由于遗留原因,他无法更改数据结构。如果他想忽略引用循环,他会(见代码中注释掉的行)......
      猜你喜欢
      • 1970-01-01
      • 2015-07-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-11-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多