web-dev-qa-db-ja.com

データ型を含むJson.NETカスタムJsonConverter

次の形式でJSONを出力するサービスを偶然見つけました。

{
    "Author": "me",
    "Version": "1.0.0",
    "data.Type1": {
        "Children": [
            {
                "data.Type1": {
                    "Children": [
                        {
                            "data.Type2": {
                                "name": "John",
                                "surname": "Doe"
                            }
                        }
                    ]
                }
            },
            {
                "data.Type3": {
                    "dob": "1990-01-01"
                }
            }
        ]
    }
}

データ型名はプロパティ名として保持され、それらの値は実際のオブジェクトです。それらはすべてdata.接頭辞で始まります。

後で取得したいのは次のようなものです。

{ // Root
    "Author": "me",
    "Version": "1.0.0",
    "Children": [ // Type1
        {
            "Children": [ // Type1
                { // Type2
                    "Name": "John",
                    "Surname": "Doe"
                }
            ]
        },
        { // Type3
            "DoB": "1990-01-01"
        }
    ]
}

次のクラスで:

class Type1 {
    ICollection<object> Children { get; set; }
}

class Type2 {
    public string Name { get; set; }
    public string Surname { get; set; }
}

class Type3 {
    public DateTime DoB { get; set; }
}

class Root 
{
    public string Author { get; set; }
    public string Version { get; set; }
    public Type1 Children { get; set; }
}

質問

データ型を考慮し、ツリーからそれらを削除して、これを追加のC#クラスに逆シリアル化するにはどうすればよいですか?

カスタムJsonConverterを試してみましたが、プロパティに属性を配置するのが最も簡単な方法であるため、コンバーターを動的に選択する方法に苦労していますが、サポートされていません。

小さな例がいいでしょう。

9
Jaka Konda

このJSON形式はやや珍しく、動的プロパティ名のために属性の使用に抵抗しますが、JsonConverterを作成して、1つの小さな変更で優先クラス構造に逆シリアル化することは可能です。 ChildrenクラスのRootプロパティをICollection<object>に変更して、Type1クラスのChildrenプロパティをミラーリングします。現状では、目的の出力(Childrenはオブジェクトではなく配列として示されています)の構造と一致しておらず、適切に処理するにはコンバーターに追加のコードが必要です。

class Root
{
    public string Author { get; set; }
    public string Version { get; set; }
    public ICollection<object> Children { get; set; }
}

これはコンバーターのために私が思いついたものです(上記の変更が行われたと仮定して):

class CustomConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(Root));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JObject obj = JObject.Load(reader);
        Root root = new Root();
        root.Author = (string)obj["Author"];
        root.Version = (string)obj["Version"];
        root.Children = ((Type1)DeserializeTypeX(obj, serializer)).Children;
        return root;
    }

    private object DeserializeTypeX(JObject obj, JsonSerializer serializer)
    {
        JProperty prop = obj.Properties().Where(p => p.Name.StartsWith("data.")).First();
        JObject child = (JObject)prop.Value;
        if (prop.Name == "data.Type1")
        {
            List<object> children = new List<object>();
            foreach (JObject jo in child["Children"].Children<JObject>())
            {
                children.Add(DeserializeTypeX(jo, serializer));
            }
            return new Type1 { Children = children };
        }
        else if (prop.Name == "data.Type2")
        {
            return child.ToObject<Type2>(serializer);
        }
        else if (prop.Name == "data.Type3")
        {
            return child.ToObject<Type3>(serializer);
        }
        throw new JsonSerializationException("Unrecognized type: " + prop.Name);
    }

    public override bool CanWrite
    {
        get { return false; }
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

このコンバーターで武装すると、次のようにクラスに逆シリアル化できます。

Root root = JsonConvert.DeserializeObject<Root>(json, new CustomConverter());

その後、次のように新しい形式にシリアル化できます。

JsonSerializerSettings settings = new JsonSerializerSettings
{
    DateFormatString = "yyyy-MM-dd",
    Formatting = Formatting.Indented
};

Console.WriteLine(JsonConvert.SerializeObject(root, settings));

フィドル: https://dotnetfiddle.net/ESNMLE

8
Brian Rogers

これが機能するかどうかはわかりませんが、Newtonsoft.Jsonを使用してオブジェクトをシリアル化し、クラスプロパティにJsonPropertyタグを含めましたか?これは、Jsonをクラスに逆シリアル化するときに機能することを知っています。

<JsonProperty("user_id")>
Public Property UserID As String
//Converts Json {user_id: 123} to class.UserID = 123

Newtonsoftでシリアル化するには、まずプロジェクトにNewtonsoftを含め、次に

Dim stringJson As String = Newtonsoft.Json.JsonConvert.SerializeObject(root)
0
Lee Reitz