2011-11-23 17 views
35

Possiedo un array JSON contenente oggetti di tipi diversi con proprietà diverse. Una delle proprietà è chiamata "tipo" e determina il tipo della voce dell'array. Ecco un esempio dei miei dati:Deserializzazione di un array JSON eterogeneo in Elenco covariante <> utilizzando JSON.NET

[{ 
     type : "comment", 
     text : "xxxx" 
    }, { 
     type : "code", 
     tokens : [{ 
       type : "ref", 
       data : "m" 
      }, { 
       type : "operator", 
       data : "e" 
      } 
     ] 
    }, { 
     type : "for", 
     boundLocal : { 
      type : "local", 
      name : "i", 
      kind : "Number" 
     }, 
     upperBound : { 
      type : "ref", 
      tokens : [{ 
        type : "operator", 
        data : "3" 
       }, { 
        type : "operator", 
        data : "0" 
       } 
      ] 
     }, 
     body : [{ 
       type : "code", 
       tokens : [{ 
         type : "ref", 
         data : "x" 
        } 
       ] 
      }, { 
       type : "code", 
       tokens : [{ 
         type : "ref", 
         data : "y" 
        } 
       } 
       ] 
     ] 
    ] 

Per mappare gli oggetti alla mia implementazione .Net io definisco un insieme di classi: una classe di base e diverse classi figlie (con una gerarchia complessa, avendo 4 "generazioni") . Qui è solo un piccolo esempio di queste classi:

public abstract class TExpression 
{ 
    [JsonProperty("type")] 
    public string Type { get; set; } 
} 

public class TComment : TExpression 
{ 
    [JsonProperty("text")] 
    public string Text { get; set; } 
} 

public class TTokenSequence : TExpression 
{ 
    [JsonProperty("tokens")] 
    public List<TToken> Tokens { get; set; } 
} 

Quello che voglio raggiungere è quello di essere in grado di deserializzare questo array in un elenco generico covariante, dichiarato come:

List<TExpression> myexpressions = JsonConvert.DeserializeObject<List<TExpression>>(aststring); 

La lista deve contenere le istanze di opportune classi figlie ereditano da TExpression, in modo da poter utilizzare il seguente codice in seguito nel mio codice:

foreach(TExpression t in myexpressions) 
{ 
    if (t is TComment) dosomething; 
    if (t is TTokenSequence) dosomethingelse; 
} 

Come posso raggiungerlo usando JSON.NET?

+0

Eventuali duplicati di https://stackoverflow.com/questions/35182949/deserialize-json-string-depending- on-type/44650012 – manuc66

risposta

44

Ecco un esempio utilizzando CustomCreationConverter.

public class JsonItemConverter : Newtonsoft.Json.Converters.CustomCreationConverter<Item> 
{ 
    public override Item Create(Type objectType) 
    { 
     throw new NotImplementedException(); 
    } 

    public Item Create(Type objectType, JObject jObject) 
    { 
     var type = (string)jObject.Property("valueType"); 
     switch (type) 
     { 
      case "int": 
       return new IntItem(); 
      case "string": 
       return new StringItem(); 
     } 

     throw new ApplicationException(String.Format("The given vehicle type {0} is not supported!", type)); 
    } 

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 
    { 
     // Load JObject from stream 
     JObject jObject = JObject.Load(reader); 

     // Create target object based on JObject 
     var target = Create(objectType, jObject); 

     // Populate the object properties 
     serializer.Populate(jObject.CreateReader(), target); 

     return target; 
    } 
} 

public abstract class Item 
{ 
    public string ValueType { get; set; } 

    [JsonProperty("valueTypeId")] 
    public int ValueTypeId { get; set; } 

    [JsonProperty("name")] 
    public string Name { get; set; } 

    public new virtual string ToString() { return "Base object, we dont' want base created ValueType=" + this.ValueType + "; " + "name: " + Name; } 
} 

public class StringItem : Item 
{ 
    [JsonProperty("value")] 
    public string Value { get; set; } 

    [JsonProperty("numberChars")] 
    public int NumberCharacters { get; set; } 

    public override string ToString() { return "StringItem object ValueType=" + this.ValueType + ", Value=" + this.Value + "; " + "Num Chars= " + NumberCharacters; } 

} 

public class IntItem : Item 
{ 
    [JsonProperty("value")] 
    public int Value { get; set; } 

    public override string ToString() { return "IntItem object ValueType=" + this.ValueType + ", Value=" + this.Value; } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     // json string 
     var json = "[{\"value\":5,\"valueType\":\"int\",\"valueTypeId\":1,\"name\":\"numberOfDups\"},{\"value\":\"some thing\",\"valueType\":\"string\",\"valueTypeId\":1,\"name\":\"a\",\"numberChars\":11},{\"value\":2,\"valueType\":\"int\",\"valueTypeId\":2,\"name\":\"b\"}]"; 

     // The above is deserialized into a list of Items, instead of a hetrogenous list of 
     // IntItem and StringItem 
     var result = JsonConvert.DeserializeObject<List<Item>>(json, new JsonItemConverter()); 

     foreach (var r in result) 
     { 
      // r is an instance of Item not StringItem or IntItem 
      Console.WriteLine("got " + r.ToString()); 
     } 
    } 
} 
+0

Non lanciare 'NotImplementedException' nel corpo del metodo astratto' Crea' causa problemi? – GregRos

0

Si potrebbe essere realizzata anche con JsonSubTypes in modo dichiarativo:

[JsonConverter(typeof(JsonSubtypes), "valueType")] 
[JsonSubtypes.KnownSubType(typeof(IntItem), "int")] 
[JsonSubtypes.KnownSubType(typeof(StringItem), "string")] 
public abstract class Item 
{ 
    public string ValueType { get; set; } 

    [JsonProperty("valueTypeId")] 
    public int ValueTypeId { get; set; } 

    [JsonProperty("name")] 
    public string Name { get; set; } 

    public override string ToString() 
    { 
     return "Base object, we dont' want base created ValueType=" + this.ValueType + "; " + "name: " + Name; 
    } 
} 

public class StringItem : Item 
{ 
    [JsonProperty("value")] 
    public string Value { get; set; } 

    [JsonProperty("numberChars")] 
    public int NumberCharacters { get; set; } 

    public override string ToString() 
    { 
     return "StringItem object ValueType=" + this.ValueType + ", Value=" + this.Value + "; " + 
       "Num Chars= " + NumberCharacters; 
    } 
} 

public class IntItem : Item 
{ 
    [JsonProperty("value")] 
    public int Value { get; set; } 

    public override string ToString() 
    { 
     return "IntItem object ValueType=" + this.ValueType + ", Value=" + this.Value; 
    } 
} 

[TestMethod] 
public void Demo() 
{ 
    // json string 
    var json = 
     "[{\"value\":5,\"valueType\":\"int\",\"valueTypeId\":1,\"name\":\"numberOfDups\"}," + 
     "{\"value\":\"some thing\",\"valueType\":\"string\",\"valueTypeId\":1,\"name\":\"a\",\"numberChars\":11}," + 
     "{\"value\":2,\"valueType\":\"int\",\"valueTypeId\":2,\"name\":\"b\"}]"; 

    var result = JsonConvert.DeserializeObject<List<Item>>(json); 

    Assert.AreEqual("IntItem object ValueType=int, Value=5", result[0].ToString()); 
    Assert.AreEqual("StringItem object ValueType=string, Value=some thing; Num Chars= 11", result[1].ToString()); 
    Assert.AreEqual("IntItem object ValueType=int, Value=2", result[2].ToString()); 
} 
Problemi correlati