web-dev-qa-db-fra.com

Quelle est la différence entre PreserveReferencesHandling et ReferenceLoopHandling dans Json.Net?

Je regarde un exemple d'application WebAPI qui a ceci codé:

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

et un autre avec ce code:

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

Ni expliquer pourquoi chacun est choisi. Je suis très nouveau sur WebAPI, alors quelqu'un peut-il m'aider en m'expliquant en termes simples quelles sont les différences et pourquoi je pourrais avoir besoin de les utiliser les unes par rapport aux autres.

57
user3568783

Ces paramètres peuvent être mieux expliqués par l'exemple. Disons que nous voulons représenter une hiérarchie d'employés dans une entreprise. Nous créons donc une classe simple comme celle-ci:

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

Il s'agit d'une petite entreprise avec seulement trois employés jusqu'à présent: Angela, Bob et Charles. Angela est la patronne, tandis que Bob et Charles sont ses subordonnés. Configurons les données pour décrire cette relation:

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

Si nous sérialisons la liste des employés en JSON ...

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

... nous obtenons cette sortie:

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

Jusqu'ici tout va bien. Vous remarquerez, cependant, que les informations pour Bob et Charles sont répétées dans le JSON car les objets qui les représentent sont référencés à la fois par la liste principale des employés et la liste des subordonnés d'Angela. C'est peut-être OK pour l'instant.

Supposons maintenant que nous aimerions également avoir un moyen de garder une trace du superviseur de chaque employé en plus de ses subordonnés. Nous changeons donc notre modèle Employee pour ajouter une propriété Supervisor ...

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

... et ajoutez quelques lignes supplémentaires à notre code de configuration pour indiquer que Charles et Bob relèvent d'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 };

Mais maintenant, nous avons un petit problème. Parce que le graphe objet contient des boucles de référence (par exemple angela références bob tandis que bob références angela), nous obtiendrons un JsonSerializationException lorsque nous essayons de sérialiser la liste des employés. Nous pouvons contourner ce problème en définissant ReferenceLoopHandling sur Ignore comme ceci:

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

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

Avec ce paramètre en place, nous obtenons le JSON suivant:

[
  {
    "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
  }
]

Si vous examinez le JSON, ce que ce paramètre doit faire est clair: chaque fois que le sérialiseur rencontre une référence à un objet qu'il est déjà en train de sérialiser, il ignore simplement ce membre. (Cela empêche le sérialiseur d'entrer dans une boucle infinie.) Vous pouvez voir que dans la liste des subordonnés d'Angela dans la partie supérieure du JSON, ni Bob ni Charles ne montrent de superviseur. Dans la partie inférieure du JSON, Bob et Charles montrent tous les deux Angela comme leur superviseur, mais notez que sa liste de subordonnés à ce stade n'inclut pas à la fois Bob et Charles.

Bien qu'il soit possible de travailler avec ce JSON et peut-être même de reconstruire la hiérarchie d'objet d'origine à partir de celui-ci avec un peu de travail, ce n'est clairement pas optimal. Nous pouvons éliminer les informations répétées dans le JSON tout en préservant les références aux objets en utilisant le paramètre PreserveReferencesHandling à la place:

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

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

Maintenant, nous obtenons le JSON suivant:

[
  {
    "$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"
  }
]

Notez que maintenant chaque objet a reçu une valeur séquentielle $id Dans le JSON. La première fois qu'un objet apparaît, il est sérialisé en totalité, tandis que les références suivantes sont remplacées par une propriété spéciale $ref Qui renvoie à l'objet d'origine avec le $id Correspondant. Avec ce paramètre en place, le JSON est beaucoup plus concis et peut être désérialisé dans la hiérarchie d'objets d'origine sans travail supplémentaire requis, en supposant que vous utilisez une bibliothèque qui comprend les $id Et $ref notation produite par Json.Net/Web API.

Alors pourquoi choisiriez-vous un réglage ou l'autre? Cela dépend bien sûr de vos besoins. Si le JSON est utilisé par un client qui ne comprend pas le format $id/$ref Et qu'il peut tolérer la présence de données incomplètes à certains endroits, vous pouvez choisir d'utiliser ReferenceLoopHandling.Ignore . Si vous recherchez un JSON plus compact et que vous utiliserez Json.Net ou l'API Web (ou une autre bibliothèque compatible) pour désérialiser les données, alors vous choisirez d'utiliser PreserveReferencesHandling.Objects. Si vos données sont un graphique acyclique dirigé sans références en double, vous n'avez besoin d'aucun paramètre.

133
Brian Rogers