2014-05-04 13 views
42

szukam w jednej próbce WebAPI aplikacji, która to zakodowanej:Jaka jest różnica między PreserveReferencesHandling a ReferenceLoopHandling w Json.Net?

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

a drugi z tym kodem:

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

Ani dlaczego każdy jest wybrany. Jestem bardzo początkującym użytkownikiem WebAPI, więc ktoś może mi pomóc, wyjaśniając mi w prosty sposób, czym są różnice i dlaczego potrzebuję użyć jednego z nich.

+4

Co mówi dokumentacja? Co było niejasne w tej kwestii? – nvoigt

Odpowiedz

98

Ustawienia te najlepiej wyjaśnić na przykładzie. Załóżmy, że chcemy reprezentować hierarchię pracowników w firmie. Tak więc tworzymy prostą klasę:

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

Jest to niewielka firma, która do tej pory zatrudniała tylko trzech pracowników: Angela, Bob i Charles. Angela jest szefem, a Bob i Charles są jej podwładnymi. Ustawmy dane, aby opisać tę zależność:

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

Gdybyśmy serializacji listę pracowników do JSON ...

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

... możemy uzyskać ten wynik:

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

Jak dotąd tak dobrze. Zauważysz jednak, że informacje dla Boba i Charlesa są powtarzane w JSON, ponieważ reprezentujące je obiekty są wymieniane zarówno przez główną listę pracowników, jak i listę podwładnych Angeli. Może na razie to jest OK.

Przypuśćmy teraz, że chcielibyśmy mieć również sposób na śledzenie każdego przełożonego pracownika oprócz jego podwładnych. Więc możemy zmienić naszą Employee model dodać obiekt Supervisor ...

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

... i dodać jeszcze kilka linii do naszego kodu instalacyjnego, aby wskazać, że Charles i Bob raport 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 }; 

Ale teraz mamy mały problem. Ponieważ wykres obiektowy zawiera pętle referencyjne (na przykład angela, odniesienia bob podczas gdy bob odnośniki angela), otrzymamy JsonSerializationException, gdy spróbujemy serializować listę pracowników. Jednym ze sposobów możemy obejść tego problemu jest ustawienie ReferenceLoopHandling do Ignore tak:

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

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

z tym ustawieniem w miejscu, otrzymujemy następujące 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 
    } 
] 

Jeśli przyjrzysz JSON, to powinno być jasne, co robi to ustawienie: za każdym razem, gdy seralizator napotka odniesienie do obiektu, który już jest w trakcie serializacji, po prostu pomija tego członka. (To uniemożliwia serializatorowi wejście w nieskończoną pętlę.) Widać to na liście podwładnych Angeli w górnej części JSON, ani Bob, ani Charles nie pokazują przełożonego. W dolnej części JSON, Bob i Charles pokazują Angela jako swojego przełożonego, ale zauważ, że jej lista podwładnych w tym momencie nie obejmuje zarówno Boba, jak i Charlesa.

Chociaż można pracować z tym JSONem, a może nawet zrekonstruować z niego oryginalną hierarchię obiektów, to z pewnością nie jest optymalna. Możemy wyeliminować powtarzające się informacje w JSON przy jednoczesnym zachowaniu odwołań do obiektów za pomocą PreserveReferencesHandling ustawienie zamiast:

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

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

Teraz otrzymujemy następujące 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" 
    } 
] 

Zauważ, że teraz każdy obiekt został przypisano sekwencyjną wartość $id w JSON. Przy pierwszym wyświetleniu obiektu jest on serializowany w całości, a kolejne odniesienia są zastępowane specjalną właściwością $ref, która odwołuje się do oryginalnego obiektu z odpowiednią wartością $id. Po wprowadzeniu tego ustawienia JSON jest znacznie bardziej zwięzły i można go ponownie przekształcić z powrotem w pierwotną hierarchię obiektów bez dodatkowej pracy, zakładając, że używasz biblioteki, która rozumie notację wygenerowaną przez Json.Net/Web API.

Dlaczego więc wybrać jedno ustawienie lub drugie? To oczywiście zależy od twoich potrzeb. Jeśli JSON zostanie zużyty przez klienta, który nie rozumie formatu $id/$ref i może tolerować niekompletne dane w miejscach, wybierz opcję ReferenceLoopHandling.Ignore. Jeśli szukasz bardziej zwartego JSON i będziesz używał Json.Net lub Web API (lub innej kompatybilnej biblioteki) do deserializacji danych, to możesz wybrać opcję PreserveReferencesHandling.Objects. Jeśli twoje dane są skierowanym acyklicznym wykresem bez powielonych odniesień, nie potrzebujesz żadnego ustawienia.

+9

Doskonała odpowiedź. Podczas korzystania z ostatniej metody ('PreserveReferencesHandling.Objects') [JsonNetDecycle] (https://bitbucket.org/smithkl42/jsonnetdecycle) jest niesamowitą biblioteką do ponownego składania referencji obiektu po stronie klienta. – WimpyProgrammer

+0

To naprawdę dobra odpowiedź. Jestem oświecony. –