2017-01-26 34 views
6

Próbuję zrozumieć, w jaki sposób JsonConvert.DeserializeObject<X>(someJsonString) jest w stanie ustawić wartości za pomocą konstruktora.Jak deserializacja JSON w C# pracy

using Newtonsoft.json 

public class X { 

    [JsonProperty("some_Property")] 
    public string SomeProperty {get;} 

    [JsonProperty("some_Property_2")] 
    public string SomeProperty2 {get;} 

    public X(string someProperty, string someProperty2) { 
     SomeProperty = someProperty; 
     SomeProperty2 = someProperty2; 
    } 

    public static X parseObject(string parseThisJson) { 
     JsonConvert.DeserializeObject<X>(someJsonString); 
    } 
} 

W powyższym kodzie chcę zrozumieć, w jaki sposób JsonConvert.DeserializeObject jest w stanie poprawnie przekształcić go w postać wielowątkową. Czy seriser json używa tego konstruktora public X(string someProperty, string someProperty2)? Jeśli tak, to w jaki sposób jest wywoływany i używany ten konstruktor?

Co się stanie, to parseThisJson ma więcej par wartości klucza oprócz some_Property i some_Property_2?

+2

możesz to sprawdzić za 1 minutę. Ustaw punkt przerwania w konstruktorze –

+0

Nie całkowicie zależy to od faktycznego typu serializacji/deserializacji. XmlSerialization na przykład * robi * używa domyślnego konstruktora. Jest to jednak bezcelowe w stosunku do faktycznego pytania tutaj. – HimBromBeere

+3

Wiesz, że to open source i możesz to sprawdzić na githubie, prawda? –

Odpowiedz

7

Po wykopaniu w źródłach Newtonsoft.Json mogę podać algorytm implementacji obiektu, który jest tam używany. I tak, konstruktor jest zawsze prawie zawsze wywoływany (*). Pytanie brzmi tylko "który?". Oto kolorowy wersja odpowiedzi:

picking constructor

TL; DR Przede wszystkim Newtonsoft.Json tworzy JsonContract typu, który masz zamiar deserializowania. To jest klasa abstrakcyjna. I ma różne implementacje dla słowników, tablic, obiektów itp. W twoim przypadku zostanie utworzona JsonObjectContract. Kontrakt zawiera różne metadane dotyczące typu zserializowanego. Najbardziej interesujące są dla nas:

  • IsInstantiable - określa czy rozszeregować typ jest chwilowe (patrz niżej)
  • Properties - to zbiór właściwości obiektu
  • DefaultCreator - domyślna metoda tworzenia używane do tworzenia obiektowi Func<object>
  • DefaultCreatorNonPublic - określa, czy domyślny konstruktor jest niepubliczny
  • OverrideCreator - kreator inny niż domyślny, stosowany, gdy aplikacja ma być JsonConstructorAttribute d konstruktora do obiektu
  • ParametrizedCreator - twórca, który nazywa paramterized konstruktora, jest ona wykorzystywana, jeśli nie mamy ani domyślnych ani nadpisywania twórców
  • CreatorParameters - zbiór właściwości, które są wykorzystywane do twórcy nadpisania lub parametryzowaną twórcy
  • MemberSerialization - ta wartość określa sposób serializowania właściwości i pól. Domyślnie jest ustawiony na OptOut - tj. Wszyscy publiczni członkowie są serializowane. Jeśli chcesz wykluczyć niektóre, powinieneś użyć atrybutu JsonIgnore. Ale jest też opcja Fields, która mówi, że wszystkie publiczne i prywatne pola powinny być serializowane. Istnieje kilka opcji włączenia tej opcji. Ale domyślnie jest wyłączony.

Niektóre z tych metadanych można pobrać, odzwierciedlając metadane typu. Na przykład. IsInstantiable oblicza się, sprawdzając, czy typ deserializacji nie jest abstrakcyjny i nie jest interfejsem. Niektóre metadane są dodawane przez DefaultContractResolver. W szczególności definiuje sposób konstruowania obiektu. W pseudo-kod:

if (contract.IsInstantiable) 
{ 
    if (type has default constructor or its a value type) 
    { 
     contract.DefaultCreator = get default (parameterless) constructor; 
     contract.DefaultCreatorNonPublic = check if default constructor public 
    } 

    if (we have constructor marked with JsonConstructorAttribute) 
    { 
     contract.OverrideCreator = constructor marked with attribute 
     contract.CreatorParameters = get properties which match constructor parameters 
    } 
    else if (contract.MemberSerialization == MemberSerialization.Fields) 
    { 
     // only if the upplication if fully trusted 
     contract.DefaultCreator = FormatterServices.GetUninitializedObject 
    } 
    else if (contract.DefaultCreator == null || contract.DefaultCreatorNonPublic) 
    { 
     if (we have one public constructor with parameters) 
     { 
       contract.ParametrizedCreator = constructor with parameters; 
       contract.CreatorParameters = get properties which match ctor parameters 
     } 
    } 
} 

Więc, jak widać prioirty idzie do konstruktora oznaczony atrybutem JsonConstructorAttribute. Otrzymasz również błąd, jeśli istnieje więcej niż jeden taki konstruktor.

(*) Dalej jest jedyny przypadek, gdy obiekt można utworzyć bez wywoływania konstruktora. Na przykład. jeśli oznaczysz klasę atrybutem [JsonObject(MemberSerialization = MemberSerialization.Fields)], aby serializować prywatne pola.

Następnie sprawdzamy, czy mamy domyślny konstruktor bez parametrów, który nie jest prywatny. Jeśli tak, to idziemy do innego konstruktora - takiego, który ma parametry i powinien być publiczny. Jeśli istnieje więcej niż jeden taki konstruktor, dostaniesz również błąd.

I ostatnia rzecz do zapamiętania - CraeatorParameters. Newtonsoft.Json używa odbicia, aby uzyskać parametry konstruktora, a następnie próbuje znaleźć najbliższe dopasowanie przez nazwę tych parametrów konstruktora do właściwości obiektu. Sprawdza także typ właściwości i parametry do dopasowania. Jeśli nie zostanie znalezione dopasowanie, to wartość domyślna zostanie przekazana do tego sparametryzowanego konstruktora.

+1

Dziękuję za wspaniałe wyjaśnienie. Mam jedno pytanie, powiedziałeś _Newtonsoft.Json używa odbicia, aby uzyskać parametry konstruktora, a następnie próbuje znaleźć najbliższe dopasowanie przez nazwę tych parametrów konstruktora do właściwości obiektu_ w tym, co rozumiesz przez dopasowanie _closest przez name_ ma nazwę właściwości don ' t musi być w 100% zgodny? na przykład SomeProprty zostanie uznany za zgodny z SomeProperty? – user2358262

+0

@ user2358262 najpierw próbuje uzyskać właściwość ze 100% pasującą nazwą, ale jeśli się nie powiedzie, to spróbuje znaleźć własność ignorując wielkość liter. Uważam, że dzieje się tak z powodu innej konwencji nazewnictwa. Dla właściwości używamy nazw PascalCase i parametrów camelCase. Możesz sprawdzić ten kod [tutaj] (https://github.com/JamesNK/Newtonsoft.Json/blob/cf6a917a46b532558578c53d34cdc4f39ec0560a/Src/Newtonsoft.Json/Serialization/JsonPropertyCollection.cs) –