2015-04-09 15 views
8

Vorremmo essere in grado di serializzare/deserializzare json da/a classi C#, con la classe principale che ha un'istanza di un oggetto figlio polimorfico. Fare ciò è facile usando l'impostazione TypeNameHandling di Auto di Json.Net. Tuttavia, vorremmo farlo senza il campo "$ type".Serializzazione Json.Net di tipo con oggetto figlio poliforfo

Il primo pensiero è di essere in grado di rinominare "$ tipo" su un valore di nostra scelta e di avere il valore per il tipo essere un enum che mapperebbe correttamente i sottotipi. Non l'ho visto come un'opzione, ma sarei lieto di sapere se è possibile.

Il secondo fu lungo le seguenti linee ... Ecco un primo passaggio a classi, con la classe di livello superiore avente un indicatore (SubTypeType) quale tipo di dati è contenuto in oggetto secondario (SubTypeData). Ho scavato un po 'nella documentazione di Json.Net e ho provato alcune cose ma non ho avuto fortuna.

Attualmente abbiamo il pieno controllo sulla definizione dei dati, ma una volta distribuito, le cose sono bloccate.

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

risposta

7

Avere le informazioni sottotipo della classe contenitore è problematico per due ragioni:

  1. Il contenitore istanza di classe non è accessibile quando Json.NET sta leggendo la classe contenuta.
  2. Se in un secondo momento è necessario convertire la proprietà SubTypeClassBase in, ad esempio, in un elenco, non sarà possibile inserire le informazioni sul sottotipo.

Invece, mi sento di raccomandare di aggiungere le informazioni sottotipo come una proprietà nella classe base:

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

Ora il sottotipo enum personalizzato verrà serializzato ogni volta che un oggetto assegnabile a SubTypeClassBase è serializzato. Fatto questo, per la deserializzazione è possibile creare un che carica il json per un dato SubTypeClassBase in un valore temporaneo JObject, controlla il valore della proprietà e deserializza l'oggetto JSON come classe appropriata.

realizzazione del prototipo di seguito:

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

che è esattamente quello che stavo cercando di fare! – GaTechThomas

+0

Grazie! Quella parte di ricorsione infinita mi stava uccidendo! –

1

si può provare con JsonSubtypes implementazione convertitore che supportano la registrazione tipo di mappatura con valori enum.

Nel tuo caso sembra che questo:

 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); 
     } 
Problemi correlati