简体   繁体   中英

Json.net: Serialisation/Deserialisation not working for ISerializable object that has circular reference

Some time back i have reported an issue for which i have got a fix in Json.net 4.5 R11.

If my circular referenced property Manager is NULL then serialisation and deserialisation works fine.

But when circular reference property Manager is set to NON NULL value then it gets ignored in the serialised string and hence it throws an exception in deserialisation.

Json.net issue base says the problem is in your code but i could not figure out. Can somebody help me here?

Questions:

  1. Is there any problem with the code below?
  2. If yes what should I do to solve the problem?
  3. If not then what should be done in Json.net code to solve this problem?

Some more updates : This is needed in the legacy application which is currently using Binary serialization. Since changes are huge, marking all the private fields that are involved in serialization with the Json serialization tag is too much work. Since Json.net can do serialization of ISerializable object we want to do this. This works if there are no circular reference objects.

My classes

[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 );
    }
}

My Test code:

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

Based on khellang 's answer, which locates the problem in JSon.Net not being able to handle circular references when using ISerializable interface implementation, you could try forcing the JSon.Net serializer to ignore the ISerializable implementation, without actually removing this implementation.
You should be able to achieve this by decorating your classes ( Department and Employee ) with JsonObject attribute.
I have not tested if this actually solves your issue.

Quoting the Serialization Guide (emphasis mine):

Types that implement ISerializable are serialized as JSON objects. When serializing, only the values returned from ISerializable.GetObjectData are used; members on the type are ignored. When deserializing, the constructor with a SerializationInfo and StreamingContext is called, passing the JSON object's values.

In situations where this behavior is not wanted, the JsonObjectAttribute can be placed on a .NET type that implements ISerializable to force it to be serialized as a normal JSON object.

As your question and comments date back to 2012, this solution might not have been available then. It's also possible, that current JSon.Net implementation is able to deal with circular references even when ISerializable is used.

I'll keep it short :)

  1. It seems that it's the ISerializable interface that screws it up. If you remove it, everything works perfectly.

  2. Do you have a REALLY good reason for implementing the ISerializable interface in Employee and Department ? If not, just remove it. If you do, GOTO 3 ;)

  3. I don't know Newtonsoft.Json internals, but somehow it's not treating circular references properly when implementing ISerializable . You should probably file an issue at GitHub .

But when circular reference property Manager is set to NON NULL value then it gets ignored in the serialised string and hence it throws an exception in deserialisation.

Because the circular reference is being ignored. That is the point of ReferenceLoopHandling.Ignore.

PreserveReferencesHandling does not work with ISerializable because child values have to be created before the parent value.

Does not look like you really need to implement the Iserializable interface. This class structure is way to simple. I have serialized very large classes without having to go that far.

If you are still having circular reference issue, try the following:

  1. I believe JSON.net has a formatting option to ignore circular references

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

  2. Ignore the offending property

  3. restructure your class

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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