2011-11-23 9 views
35

Tengo una matriz JSON que contiene objetos de diferentes tipos con diferentes propiedades. Una de las propiedades se llama "tipo" y determina el tipo de elemento de la matriz. Este es un ejemplo de mis datos:Deserialización de matriz JSON heterogénea en la lista covariante <> con 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" 
        } 
       } 
       ] 
     ] 
    ] 

Para asignar los objetos a mi aplicación .Net que definen un conjunto de clases: una clase base y varias clases hijas (con una jerarquía compleja, que tiene 4 "generaciones") . Aquí es sólo un pequeño ejemplo de estas clases:

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

Lo que quiero llegar es ser capaz de deserializar esta matriz en una lista genérica covariante, declarado como:

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

Esta lista debe contener las instancias de clases apropiados para niños que heredan de TExpression, por lo que puede utilizar el siguiente código más adelante en mi código:

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

¿Cómo puedo llegar a ella usando JSON.NET?

+0

duplicado Posible de https://stackoverflow.com/questions/35182949/deserialize-json-string-depending- on-type/44650012 – manuc66

Respuesta

44

Aquí hay un ejemplo con 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

No arrojará 'NotImplementedException' en el cuerpo del método abstracto 'Create' causar problemas? – GregRos

0

También se podría implementar con JsonSubTypes de una manera declarativa:

[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()); 
} 
Cuestiones relacionadas