What is the difference between PreserveReferencesHandling and ReferenceLoopHandling in Json.Net?

user3568783 picture user3568783 · May 4, 2014 · Viewed 20.7k times · Source

I am looking at one WebAPI application sample that has this coded:

json.SerializerSettings.PreserveReferencesHandling 
   = Newtonsoft.Json.PreserveReferencesHandling.Objects;

and another with this coded:

json.SerializerSettings.ReferenceLoopHandling 
   = Newtonsoft.Json.ReferenceLoopHandling.Ignore;

Neither explain why each is chosen. I'm very new to WebAPI, so can someone help by explaining to me in simple terms what the differences are and why I might need to use one over the other.

Answer

Brian Rogers picture Brian Rogers · May 4, 2014

These settings can best be explained by example. Let's say that we want to represent a hierarchy of employees in a company. So we make a simple class like this:

class Employee
{
    public string Name { get; set; }
    public List<Employee> Subordinates { get; set; }
}

This is a small company with only three employees so far: Angela, Bob and Charles. Angela is the boss, while Bob and Charles are her subordinates. Let's set up the data to describe this relationship:

Employee angela = new Employee { Name = "Angela Anderson" };
Employee bob = new Employee { Name = "Bob Brown" };
Employee charles = new Employee { Name = "Charles Cooper" };

angela.Subordinates = new List<Employee> { bob, charles };

List<Employee> employees = new List<Employee> { angela, bob, charles };

If we serialize the list of employees to JSON...

string json = JsonConvert.SerializeObject(employees, Formatting.Indented);
Console.WriteLine(json);

...we get this output:

[
  {
    "Name": "Angela Anderson",
    "Subordinates": [
      {
        "Name": "Bob Brown",
        "Subordinates": null
      },
      {
        "Name": "Charles Cooper",
        "Subordinates": null
      }
    ]
  },
  {
    "Name": "Bob Brown",
    "Subordinates": null
  },
  {
    "Name": "Charles Cooper",
    "Subordinates": null
  }
]

So far so good. You'll notice, however, that the information for Bob and Charles is repeated in the JSON because the objects representing them are referenced both by the main list of employees and Angela's list of subordinates. Maybe that's OK for now.

Now suppose we'd also like to have a way to keep track of each Employee's supervisor in addition to his or her subordinates. So we change our Employee model to add a Supervisor property...

class Employee
{
    public string Name { get; set; }
    public Employee Supervisor { get; set; }
    public List<Employee> Subordinates { get; set; }
}

...and add a couple more lines to our setup code to indicate that Charles and Bob report to Angela:

Employee angela = new Employee { Name = "Angela Anderson" };
Employee bob = new Employee { Name = "Bob Brown" };
Employee charles = new Employee { Name = "Charles Cooper" };

angela.Subordinates = new List<Employee> { bob, charles };
bob.Supervisor = angela;       // added this line
charles.Supervisor = angela;   // added this line

List<Employee> employees = new List<Employee> { angela, bob, charles };

But now we have a bit of a problem. Because the object graph has reference loops in it (e.g. angela references bob while bob references angela), we will get a JsonSerializationException when we try to serialize the employees list. One way we can get around this issue is by setting ReferenceLoopHandling to Ignore like this:

JsonSerializerSettings settings = new JsonSerializerSettings
{
    ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
    Formatting = Formatting.Indented
};

string json = JsonConvert.SerializeObject(employees, settings);

With this setting in place, we get the following JSON:

[
  {
    "Name": "Angela Anderson",
    "Supervisor": null,
    "Subordinates": [
      {
        "Name": "Bob Brown",
        "Subordinates": null
      },
      {
        "Name": "Charles Cooper",
        "Subordinates": null
      }
    ]
  },
  {
    "Name": "Bob Brown",
    "Supervisor": {
      "Name": "Angela Anderson",
      "Supervisor": null,
      "Subordinates": [
        {
          "Name": "Charles Cooper",
          "Subordinates": null
        }
      ]
    },
    "Subordinates": null
  },
  {
    "Name": "Charles Cooper",
    "Supervisor": {
      "Name": "Angela Anderson",
      "Supervisor": null,
      "Subordinates": [
        {
          "Name": "Bob Brown",
          "Subordinates": null
        }
      ]
    },
    "Subordinates": null
  }
]

If you examine the JSON, it should be clear what this setting does: any time the serializer encounters a reference back to an object it is already in the process of serializing, it simply skips that member. (This prevents the serializer from getting into an infinite loop.) You can see that in Angela's list of subordinates in the top part of the JSON, neither Bob nor Charles show a supervisor. In the bottom part of the JSON, Bob and Charles both show Angela as their supervisor, but notice her subordinates list at that point does not include both Bob and Charles.

While it is possible to work with this JSON and maybe even reconstruct the original object hierarchy from it with some work, it is clearly not optimal. We can eliminate the repeated information in the JSON while still preserving the object references by using the PreserveReferencesHandling setting instead:

JsonSerializerSettings settings = new JsonSerializerSettings
{
    PreserveReferencesHandling = PreserveReferencesHandling.Objects,
    Formatting = Formatting.Indented
};

string json = JsonConvert.SerializeObject(employees, settings);

Now we get the following JSON:

[
  {
    "$id": "1",
    "Name": "Angela Anderson",
    "Supervisor": null,
    "Subordinates": [
      {
        "$id": "2",
        "Name": "Bob Brown",
        "Supervisor": {
          "$ref": "1"
        },
        "Subordinates": null
      },
      {
        "$id": "3",
        "Name": "Charles Cooper",
        "Supervisor": {
          "$ref": "1"
        },
        "Subordinates": null
      }
    ]
  },
  {
    "$ref": "2"
  },
  {
    "$ref": "3"
  }
]

Notice that now each object has been assigned a sequential $id value in the JSON. The first time that an object appears, it is serialized in full, while subsequent references are replaced with a special $ref property that refers back to the original object with the corresponding $id. With this setting in place, the JSON is much more concise and can be deserialized back into the original object hierarchy with no additional work required, assuming you are using a library that understands the $id and $ref notation produced by Json.Net / Web API.

So why would you choose one setting or the other? It depends on your needs of course. If the JSON will be consumed by a client that does not understand the $id/$ref format, and it can tolerate having incomplete data in places, you would choose to use ReferenceLoopHandling.Ignore. If you're looking for more compact JSON and you will be using Json.Net or Web API (or another compatible library) to deserialize the data, then you would choose to use PreserveReferencesHandling.Objects. If your data is a directed acyclic graph with no duplicate references then you don't need either setting.