web-dev-qa-db-ja.com

Json.NETを使用して異種JSON配列を共変リスト<>に逆シリアル化する

さまざまなプロパティを持つさまざまなタイプのオブジェクトを含むJSON配列があります。プロパティの1つは「タイプ」と呼ばれ、配列アイテムのタイプを決定します。これが私のデータの例です:

   [{
        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実装にマップするために、クラスのセットを定義します。1つの基本クラスと複数の子クラス(複雑な階層で、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を使用してアクセスするにはどうすればよいですか?

40

これは、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());
        }
    }
}
49
JimSan

また、宣言的な方法で私の 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
manuc66

CustomCreationConverter はこれを処理できるはずです。

2
twaggs