Qual è la differenza tra PreserveReferencesHandling e ReferenceLoopHandling in Json.Net?

Sto guardando un esempio di applicazione WebAPI che ha questo codice:

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

e un altro con questo codice:

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

Nessuno dei due spiega perché ognuno è scelto. Sono molto nuovo a WebAPI, quindi qualcuno può aiutarmi spiegandomi in termini semplici quali sono le differenze e perché potrei aver bisogno di usarne una sull’altra.

Queste impostazioni possono essere meglio spiegate con l’esempio. Diciamo che vogliamo rappresentare una gerarchia di dipendenti in un’azienda. Quindi creiamo una class semplice come questa:

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

Questa è una piccola azienda con solo tre impiegati finora: Angela, Bob e Charles. Angela è il capo, mentre Bob e Charles sono i suoi subordinati. Impostiamo i dati per descrivere questa relazione:

 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 { bob, charles }; List employees = new List { angela, bob, charles }; 

Se serializziamo la lista dei dipendenti in JSON …

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

… otteniamo questo risultato:

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

Fin qui tutto bene. Noterai, tuttavia, che le informazioni per Bob e Charles vengono ripetute nel JSON perché gli oggetti che li rappresentano sono referenziati sia dalla lista principale dei dipendenti che dalla lista dei subordinati di Angela. Forse è OK per ora.

Ora supponiamo che vorremmo anche avere un modo per tenere traccia del supervisore di ciascun dipendente oltre ai suoi subordinati. Quindi modifichiamo il modello Employee per aggiungere una proprietà Supervisor

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

… e aggiungi un paio di righe al nostro codice di configurazione per indicare che Charles e Bob riportano ad 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 { bob, charles }; bob.Supervisor = angela; // added this line charles.Supervisor = angela; // added this line List employees = new List { angela, bob, charles }; 

Ma ora abbiamo un po ‘di problemi. Poiché il grafico dell’object ha dei loop di riferimento (ad es. I riferimenti angela bob mentre i riferimenti bob angela ), otterremo una JsonSerializationException quando proviamo a serializzare la lista dei dipendenti. Un modo per aggirare questo problema è impostare ReferenceLoopHandling su Ignore questo modo:

 JsonSerializerSettings settings = new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore, Formatting = Formatting.Indented }; string json = JsonConvert.SerializeObject(employees, settings); 

Con questa impostazione, otteniamo il seguente 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 } ] 

Se si esamina il JSON, dovrebbe essere chiaro che cosa fa questa impostazione: ogni volta che il serializzatore incontra un riferimento a un object che è già in fase di serializzazione, salta semplicemente quel membro. (Questo impedisce al serializzatore di entrare in un ciclo infinito.) Puoi vedere che nella lista dei subordinati di Angela nella parte superiore del JSON, né Bob né Charles mostrano un supervisore. Nella parte inferiore del JSON, Bob e Charles mostrano entrambi Angela come loro supervisore, ma notano che la lista dei suoi subordinati a quel punto non include sia Bob che Charles.

Anche se è ansible lavorare con questo JSON e forse anche ribuild la gerarchia dell’object originale con un po ‘di lavoro, non è chiaramente ottimale. Possiamo eliminare le informazioni ripetute nel JSON conservando i riferimenti agli oggetti usando invece l’impostazione PreserveReferencesHandling :

 JsonSerializerSettings settings = new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects, Formatting = Formatting.Indented }; string json = JsonConvert.SerializeObject(employees, settings); 

Ora otteniamo il seguente 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" } ] 

Si noti che ora a ogni object è stato assegnato un valore $id sequenziale nel JSON. La prima volta che viene visualizzato un object, viene serializzato per intero, mentre i riferimenti successivi vengono sostituiti con una speciale proprietà $ref che rimanda all’object originale con il corrispondente $id . Con questa impostazione, il JSON è molto più conciso e può essere deserializzato nella gerarchia degli oggetti originale senza richiedere altro lavoro, presupponendo che si stia utilizzando una libreria che comprende l’ $id $ref e la notazione $ref prodotta da Json.Net / Web API.

Allora perché sceglieresti un setting o l’altro? Dipende dalle tue esigenze, naturalmente. Se il JSON sarà consumato da un client che non comprende il formato $id / $ref e può tollerare di avere dati incompleti in luoghi, si sceglierà di utilizzare ReferenceLoopHandling.Ignore . Se stai cercando un JSON più compatto e utilizzerai Json.Net o Web API (o un’altra libreria compatibile) per deserializzare i dati, allora sceglieresti PreserveReferencesHandling.Objects . Se i tuoi dati sono un grafico aciclico diretto senza riferimenti duplicati, non hai bisogno di nessuna impostazione.