2012-12-19 5 views
16

Używam Json.NET w mojej usługi danych WCF.Jak deserializować klasy bez wywoływania konstruktora?

Oto moja klasa (uproszczony):

[DataContract] 
public class Component 
{ 
    public Component() 
    { 
     // I'm doing some magic here. 
    } 
} 

Jak mogę deserializowania tej klasy bez wywoływania konstruktora używając JsonConvert.DeserializeObject?

Przepraszamy, jeśli nie jest jasne, zadaj pytanie.

+0

Niemożliwe AFAIK. Konstruktor jest zawsze wykonywany podczas tworzenia instancji. – Maarten

+3

@Maarten http://msdn.microsoft.com/en-us/library/system.runtime.serialization.formatterservices.getsafeuninitializedobject.aspx –

+0

Thanx! Nauczyłem się czegoś dzisiaj :-) – Maarten

Odpowiedz

7
  1. Można utworzyć klasę, która dziedziczy CustomCreationConverter i używać FormatterServices.GetSafeUninitializedObject stworzyć swój obiekt . Pomija wywołanie konstruktora.

    Więcej informacji o CustomCreationConverter here.

  2. Umieszczenie [JsonObject(MemberSerialization.Fields)] w klasie uczyni Json.NET używać FormatterServices.GetSafeUninitializedObject domyślnie (choć tryb Fields będzie również serializacji pól publicznych/prywatnych zamiast publicznych właściwości, które może nie chcemy).

  3. Przenieś logikę, która nie ma być uruchamiana poza domyślnym konstruktorem.

+0

Dziękuję bardzo! Z jakiegoś powodu, kiedy próbowałem '[JsonObject (MemberSerialization.Fields)]', stworzyło 'StackOverflowException'. Więc wolę pierwszy wariant. Czy możesz mi powiedzieć - czy istnieje sposób na stworzenie uniwersalnego 'CustomCreationConverter' dla wielu klas? –

+1

Skopiuj kod źródłowy CustomCreationConverter i zmodyfikuj go, aby zrobić to, co chcesz. To po prostu dziedziczy z JsonConverter - https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Converters/CustomCreationConverter.cs –

10

Konstruktor jest zawsze wywoływany. Zazwyczaj mam dwa konstruktory. Jeden dla serializacji (domyślny konstruktor) i jeden dla wszystkich „regularnej” Kod:

[DataContract] 
public class Component 
{ 
    // for JSON.NET 
    protected Component() 
    { 
    } 

    public Component(allMandatoryFieldsHere) 
    { 
     // I'm doing some magic here. 
    } 
} 

W ten sposób można również upewnić się, że dev podać wszystkie informacje, które są wymagane.

Nie polecam jednak używania niczego poza DTO podczas przesyłania informacji, ponieważ w przeciwnym razie można obejść hermetyzację obiektów (każdy może zainicjować dowolne pole dowolną wartością). Dobrze. Jeśli używasz tylko modeli anemicznych.

jest w związku z tym brzydkim rozwiązaniem, ponieważ nikt nie może powiedzieć, że tworzysz wszystkie obiekty w niezainicjowany sposób. Inicjalizacja konstruktora jest nie bez powodu. Lepiej, aby klasy mogły stwierdzić, że nie można wywoływać prawdziwego konstruktora, dostarczając konstruktora "serializacji", jak sugerowałem.

+0

Ale o ile mi wiadomo, domyślne 'DataContractJsonSerializer' nie wywołuje konstruktora. –

+0

@IgorShastin: Dlatego powinieneś używać go tylko z DTO. JSON.NET bardziej się zachowuje. – jgauffin

0

Najlepszym rozwiązaniem, aby uniknąć wywołania konstruktora na deserializacji jest stworzenie specjalnego resolverowi kontrakt, który zastępuje funkcję twórca dla wszystkich klas bez konstruktora oznaczonego atrybutem JsonConstructor. W ten sposób nadal możesz wymusić na JSON.NET wywołanie konstruktora, jeśli naprawdę go potrzebujesz, ale wszystkie inne klasy będą tworzone podobnie jak w standardowych serializerach DataContract w .NET. Oto kod:

/// <summary> 
/// Special contract resolver to create objects bypassing constructor call. 
/// </summary> 
public class NoConstructorCreationContractResolver : DefaultContractResolver 
{ 
    /// <summary> 
    /// Creates a <see cref="T:Newtonsoft.Json.Serialization.JsonObjectContract"/> for the given type. 
    /// </summary> 
    /// <param name="objectType">Type of the object.</param> 
    /// <returns> 
    /// A <see cref="T:Newtonsoft.Json.Serialization.JsonObjectContract"/> for the given type. 
    /// </returns> 
    protected override JsonObjectContract CreateObjectContract(Type objectType) 
    { 
     // prepare contract using default resolver 
     var objectContract = base.CreateObjectContract(objectType); 

     // if type has constructor marked with JsonConstructor attribute or can't be instantiated, return default contract 
     if (objectContract.OverrideConstructor != null || objectContract.CreatedType.IsInterface || objectContract.CreatedType.IsAbstract) 
      return objectContract; 

     // prepare function to check that specified constructor parameter corresponds to non writable property on a type 
     Func<JsonProperty, bool> isParameterForNonWritableProperty = 
      parameter => 
      { 
       var propertyForParameter = objectContract.Properties.FirstOrDefault(property => property.PropertyName == parameter.PropertyName); 

       if (propertyForParameter == null) 
        return false; 

       return !propertyForParameter.Writable; 
      };     

     // if type has parameterized constructor and any of constructor parameters corresponds to non writable property, return default contract 
     // this is needed to handle special cases for types that can be initialized only via constructor, i.e. Tuple<> 
     if (objectContract.ParametrizedConstructor != null 
      && objectContract.ConstructorParameters.Any(parameter => isParameterForNonWritableProperty(parameter))) 
      return objectContract; 

     // override default creation method to create object without constructor call 
     objectContract.DefaultCreatorNonPublic = false; 
     objectContract.DefaultCreator =() => FormatterServices.GetSafeUninitializedObject(objectContract.CreatedType); 

     return objectContract; 
    } 
} 

Wszystko czego potrzebujesz to po prostu ustaw ten resolver umowy w ustawieniach serializera przed deserializacją.

1

Inni już wspomnieli o drugim konstruktorze, ale używając 2 atrybutów: [JsonConstructor] i [Obsolete], można zrobić o wiele lepiej, niż pozostawienie tego ludziom, aby pamiętać, do którego należy zadzwonić.

public ChatMessage() 
    { 
     MessageID = ApplicationState.GetNextChatMessageID(); // An expensive call that uses up an otherwise free ID from a limited set and does disk access in the process. 
    } 


    [JsonConstructor] // This forces JsonSerializer to call it instead of the default. 
    [Obsolete("Call the default constructor. This is only for JSONserializer", true)] // To make sure that calling this from your code directly will generate a compiler error. JSONserializer can still call it because it does it via reflection. 
    public ChatMessage(bool DO_NOT_CALL_THIS) 
    { 
    } 

[JsonConstructor] wymusza na JsonSerializer wywołanie go zamiast domyślnego.
[Przestarzałe ("...", prawda)] Upewnia się, że wywołanie tego bezpośrednio z kodu wygeneruje błąd kompilatora. JSONserializer może nadal go wywoływać, ponieważ robi to poprzez odbicie.