Click or drag to resize
Json.NET

Preserving Object References

 

By default Json.NET will serialize all objects it encounters by value. If a list contains two Person references and both references point to the same object, then the JsonSerializer will write out all the names and values for each reference.

Preserve Object References Off
Person p = new Person
{
    BirthDate = new DateTime(1980, 12, 23, 0, 0, 0, DateTimeKind.Utc),
    LastModified = new DateTime(2009, 2, 20, 12, 59, 21, DateTimeKind.Utc),
    Name = "James"
};

List<Person> people = new List<Person>();
people.Add(p);
people.Add(p);

string json = JsonConvert.SerializeObject(people, Formatting.Indented);
//[
//  {
//    "Name": "James",
//    "BirthDate": "1980-12-23T00:00:00Z",
//    "LastModified": "2009-02-20T12:59:21Z"
//  },
//  {
//    "Name": "James",
//    "BirthDate": "1980-12-23T00:00:00Z",
//    "LastModified": "2009-02-20T12:59:21Z"
//  }
//]

In most cases this is the desired result, but in certain scenarios writing the second item in the list as a reference to the first is a better solution. If the above JSON was deserialized now, then the returned list would contain two completely separate Person objects with the same values. Writing references by value will also cause problems on objects where a circular reference occurs.

PreserveReferencesHandling

Setting PreserveReferencesHandling will track object references when serializing and deserializing JSON.

Preserve Object References On
string json = JsonConvert.SerializeObject(people, Formatting.Indented,
    new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects });

//[
//  {
//    "$id": "1",
//    "Name": "James",
//    "BirthDate": "1983-03-08T00:00Z",
//    "LastModified": "2012-03-21T05:40Z"
//  },
//  {
//    "$ref": "1"
//  }
//]

List<Person> deserializedPeople = JsonConvert.DeserializeObject<List<Person>>(json,
    new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects });

Console.WriteLine(deserializedPeople.Count);
// 2

Person p1 = deserializedPeople[0];
Person p2 = deserializedPeople[1];

Console.WriteLine(p1.Name);
// James
Console.WriteLine(p2.Name);
// James

bool equal = Object.ReferenceEquals(p1, p2);
// true

The first Person in the list is serialized with the addition of an object ID. The second Person in JSON is now only a reference to the first.

With PreserveReferencesHandling on, now only one Person object is created on deserialization and the list contains two references to it, mirroring what we started with.

Metadata properties like $id must be located at the beginning of a JSON object to be successfully detected during deserialization. If you can't control the order of properties in your JSON object then MetadataPropertyHandling can be used to remove this restriction.

Note Note

References cannot be preserved when a value is set via a non-default constructor. With a non-default constructor, child values must be created before the parent value so they can be passed into the constructor, making tracking reference impossible. ISerializable types are an example of a class whose values are populated with a non-default constructor and won't work with PreserveReferencesHandling.

IsReference

The PreserveReferencesHandling setting on the JsonSerializer will change how all objects are serialized and deserialized. For fine grain control over which objects and members should be serialized as a reference there is the IsReference property on the JsonObjectAttribute, JsonArrayAttribute and JsonPropertyAttribute.

Setting IsReference on JsonObjectAttribute or JsonArrayAttribute to true will mean the JsonSerializer will always serialize the type the attribute is against as a reference. Setting IsReference on the JsonPropertyAttribute to true will serialize only that property as a reference.

IsReference
[JsonObject(IsReference = true)]
public class EmployeeReference
{
    public string Name { get; set; }
    public EmployeeReference Manager { get; set; }
}
IReferenceResolver

To customize how references are generated and resolved the IReferenceResolver interface is available to inherit from and use with the JsonSerializer.

See Also