2015-04-09 19 views
8

Chcielibyśmy móc serializować/deserializować json z/do klas C#, przy czym główna klasa ma instancję polimorficznego obiektu podrzędnego. Takie postępowanie jest łatwe przy użyciu ustawienia TypeNameHandling.Auto Json.Net. Jednak chcielibyśmy to zrobić bez pola "$ type".Serializacja typu Json.Net z polimorficznym obiektem potomnym

Pierwsza myśl polega na tym, aby zmienić nazwę "$ typ" na wybraną przez nas wartość, a wartość typu powinna być wylicznikiem, który prawidłowo mapowałby podtypy. Nie widziałem tego jako opcji, ale chętnie usłyszę, jeśli to możliwe.

Druga myśl była wzdłuż następujących linii ... Poniżej znajduje się pierwsze przejście na klasach, z klasą najwyższego poziomu posiadającą wskaźnik (SubTypeType) określający rodzaj danych zawartych w obiekcie podrzędnym (SubTypeData). Wkopałem się trochę w dokumentację Json.Net i próbowałem kilku rzeczy, ale nie miałem szczęścia.

Obecnie mamy pełną kontrolę nad definicją danych, ale po jej wdrożeniu są zablokowane.

public class MainClass 
{ 
    public SubType   SubTypeType { get; set; } 
    public SubTypeClassBase SubTypeData { get; set; } 
} 

public class SubTypeClassBase 
{ 
} 

public class SubTypeClass1 : SubTypeClassBase 
{ 
    public string AaaField { get; set; } 
} 

public class SubTypeClass2 : SubTypeClassBase 
{ 
    public string ZzzField { get; set; } 
} 

Odpowiedz

7

Mając informacje podtypu w klasie pojemnika jest problematyczne z dwóch powodów:

  1. Pojemnik instancja klasy nie jest dostępna, jeśli Json.NET czyta zawartej klasę.
  2. Jeśli później musisz przekonwertować właściwość SubTypeClassBase na, powiedzmy, listę, nie będzie gdzie nigdzie umieścić informacji o podtypach.

Zamiast tego, polecam dodanie informacji podtypu jako właściwości w klasie bazowej:

[JsonConverter(typeof(SubTypeClassConverter))] 
public class SubTypeClassBase 
{ 
    [JsonConverter(typeof(StringEnumConverter))] // Serialize enums by name rather than numerical value 
    public SubType Type { get { return typeToSubType[GetType()]; } } 
} 

Teraz zwyczaj podtyp wyliczenia będą szeregowane gdy przedmiotem cesji na SubTypeClassBase jest seryjny. Po wykonaniu tej czynności dla utworzenia deserializacji można utworzyć plik JsonConverter, który ładuje json dla danego SubTypeClassBase do tymczasowego JObject, sprawdza wartość właściwości i deserializuje obiekt JSON jako odpowiednią klasę.

realizacja Prototype poniżej:

public enum SubType 
{ 
    BaseType, 
    Type1, 
    Type2, 
} 

[JsonConverter(typeof(SubTypeClassConverter))] 
public class SubTypeClassBase 
{ 
    static readonly Dictionary<Type, SubType> typeToSubType; 
    static readonly Dictionary<SubType, Type> subTypeToType; 

    static SubTypeClassBase() 
    { 
     typeToSubType = new Dictionary<Type,SubType>() 
     { 
      { typeof(SubTypeClassBase), SubType.BaseType }, 
      { typeof(SubTypeClass1), SubType.Type1 }, 
      { typeof(SubTypeClass2), SubType.Type2 }, 
     }; 
     subTypeToType = typeToSubType.ToDictionary(pair => pair.Value, pair => pair.Key); 
    } 

    public static Type GetType(SubType subType) 
    { 
     return subTypeToType[subType]; 
    } 

    [JsonConverter(typeof(StringEnumConverter))] // Serialize enums by name rather than numerical value 
    public SubType Type { get { return typeToSubType[GetType()]; } } 
} 

public class SubTypeClass1 : SubTypeClassBase 
{ 
    public string AaaField { get; set; } 
} 

public class SubTypeClass2 : SubTypeClassBase 
{ 
    public string ZzzField { get; set; } 
} 

public class SubTypeClassConverter : JsonConverter 
{ 
    public override bool CanConvert(Type objectType) 
    { 
     return objectType == typeof(SubTypeClassBase); 
    } 

    public override bool CanWrite { get { return false; } } 

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 
    { 
     var token = JToken.Load(reader); 
     var typeToken = token["Type"]; 
     if (typeToken == null) 
      throw new InvalidOperationException("invalid object"); 
     var actualType = SubTypeClassBase.GetType(typeToken.ToObject<SubType>(serializer)); 
     if (existingValue == null || existingValue.GetType() != actualType) 
     { 
      var contract = serializer.ContractResolver.ResolveContract(actualType); 
      existingValue = contract.DefaultCreator(); 
     } 
     using (var subReader = token.CreateReader()) 
     { 
      // Using "populate" avoids infinite recursion. 
      serializer.Populate(subReader, existingValue); 
     } 
     return existingValue; 
    } 

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 
    { 
     throw new NotImplementedException(); 
    } 
} 
+0

To jest dokładnie to, co starałem się zrobić! – GaTechThomas

+0

Dziękujemy! Ta nieskończona rekurencja zabijała mnie! –

1

Można spróbować z JsonSubtypes realizacji konwerter, który obsługuje rejestracji typu mapowania enum wartościami.

W twoim przypadku wygląda to tak:

 public class MainClass 
     { 
      public SubTypeClassBase SubTypeData { get; set; } 
     } 

     [JsonConverter(typeof(JsonSubtypes), "SubTypeType")] 
     [JsonSubtypes.KnownSubType(typeof(SubTypeClass1), SubType.WithAaaField)] 
     [JsonSubtypes.KnownSubType(typeof(SubTypeClass2), SubType.WithZzzField)] 
     public class SubTypeClassBase 
     { 
      public SubType SubTypeType { get; set; } 
     } 

     public class SubTypeClass1 : SubTypeClassBase 
     { 
      public string AaaField { get; set; } 
     } 

     public class SubTypeClass2 : SubTypeClassBase 
     { 
      public string ZzzField { get; set; } 
     } 

     public enum SubType 
     { 
      WithAaaField, 
      WithZzzField 
     } 

     [TestMethod] 
     public void Deserialize() 
     { 
      var obj = JsonConvert.DeserializeObject<MainClass>("{\"SubTypeData\":{\"ZzzField\":\"zzz\",\"SubTypeType\":1}}"); 
      Assert.AreEqual("zzz", (obj.SubTypeData as SubTypeClass2)?.ZzzField); 
     }