使用 Json.NET 将异构 JSON 数组反序列化为协变 List<>

Posted

技术标签:

【中文标题】使用 Json.NET 将异构 JSON 数组反序列化为协变 List<>【英文标题】:Deserializing heterogenous JSON array into covariant List<> using Json.NET 【发布时间】:2012-01-04 16:45:07 【问题描述】:

我有一个 JSON 数组,其中包含具有不同属性的不同类型的对象。其中一个属性称为“类型”,它确定数组项的类型。这是我的数据示例:

   [
        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"
                    
                
                ]
        ]
    ]

为了将这些对象映射到我的 .Net 实现,我定义了一组类:一个基类和几个子类(具有复杂的层次结构,具有 4 个“代”)。这里只是这些类的一个小例子:

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; 

我想要达到的是能够将这个数组反序列化为一个协变的泛型列表,声明为:

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

此列表应包含从 TExpression 继承的适当子类的实例,因此我可以稍后在我的代码中使用以下代码:

foreach(TExpression t in myexpressions)

    if (t is TComment) dosomething;
    if (t is TTokenSequence) dosomethingelse;

如何使用 JSON.NET 访问它?

【问题讨论】:

***.com/questions/35182949/…的可能重复 【参考方案1】:

这是一个使用 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());
        
    

【讨论】:

不会在抽象 Create 方法的主体中抛出 NotImplementedException 导致问题吗?【参考方案2】:

也可以用我的JsonSubTypes库以声明的方式实现它:

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

【讨论】:

【参考方案3】:

CustomCreationConverter 应该能够处理这个问题。

【讨论】:

以上是关于使用 Json.NET 将异构 JSON 数组反序列化为协变 List<>的主要内容,如果未能解决你的问题,请参考以下文章

使用来自 URL 的 JSON.NET 反序列化 JSON 数组

获取数组 JSON.Net 的长度

使用 Json.net 反序列化 JSON 对象数组

如何使用 json.net 将 json 数组添加到 JObject 的属性中

使用 Json.NET (Newtonsoft) 将空数组从 JSON 转换为 XML

JSON.Net 将集合序列化为数组数组