68113


C#のJsonデータのデシリアライズについての質問となります。  
連番された項目名のオブジェクトをListにしてデシリアライズする良い方法がないか、調査中です。  
良い方法があれば、ご教授して頂ければと思います。

**前提**  
例えば、これまでは下記のような一部が配列となるjsonを処理する必要があり、

[code]

    {
        "textData": "textVal",
        "numData" : 123,
        "objData": [
            {
                "hogeText": "hogeTextVal1",
                "hogeNum" : 1,
                "hogeobj": {
                    "hoge": "hogeVal1",
                    "hage": "hageVal1"
                }
            },
            {
                "hogeText": "hogeTextVal2",
                "hogeNum" : 2,
                "hogeobj": {
                    "hoge": "hogeVal2",
                    "hage": "hageVal2"
                }
            },
            {
                "hogeText": "hogeTextVal3",
                "hogeNum" : 3,
                "hogeobj": {
                    "hoge": "hogeVal3",
                    "hage": "hageVal3"
                }
            }
        ]
    }
    
[/code]

下記Entityクラスを準備して、「JsonSerializer.Deserialize(json);」(namespace:
System.Text.Json)でデシリアライズを行っていました。  
※課題が解決できれば、デシリアライズの方法にこだわりはありません。

[code]

    public class entity
    {
        public class Root
        {
            public string textData { get; set; }
    
            public long numData { get; set; }
    
            public ObjDatum[] objData { get; set; }
        }
    
        public class ObjDatum
        {
            public string hogeText { get; set; }
    
            public long hogeNum { get; set; }
    
            public Hogeobj hogeobj { get; set; }
        }
    
        public class Hogeobj
        {
            public string hoge { get; set; }
    
            public string hage { get; set; }
        }
    }
    
[/code]

**質問**  
前提のような一部配列のjsonではなく、下記のようにオブジェクト名が連番で変動するデータの場合、前提と同様にデシリアライズ時にListにする方法はありますか。  
(前提の配列objData部が、objData1、objData2、objData3...ようになる)

[code]

    {
        "textData": "textVal",
        "numData" : 123,
        "objData1": {
            "hogeText": "hogeTextVal1",
            "hogeNum" : 1,
            "hogeobj1": {
                "hoge": "hogeVal1",
                "hage": "hageVal1"
            }
        },
        "objData2": {
            "hogeText": "hogeTextVal2",
            "hogeNum" : 2,
            "hogeobj2": {
                "hoge": "hogeVal2",
                "hage": "hageVal2"
            }
        },
        "objData3": {
            "hogeText": "hogeTextVal3",
            "hogeNum" : 3,
            "hogeobj3": {
                "hoge": "hogeVal3",
                "hage": "hageVal3"
            }
        }
    }
    
[/code]

実際には3つだけではなく、数10、数100というobjDataがあるため、1つずつクラスを作成する方法は避けたいと思っています。  
また、ネストされたhogeobjも連番で項目名が変動いたします。デシリアライズされた後にListをさらに処理・加工するため、プロパティ名は揃っていて欲しいです。

以上、よろしくお願いいたします。


CC BY-SA 4.0
2020-06-29T06:31:46.940
68103
2020-06-29T23:41:41.773
2020-06-29T11:27:34.217
3060
40858
question
c# .net json
C#(.net)のjsonデータのデシリアライズについて


構成が複雑なので、ある程度ゴリ押しのコードで達成することになると予想しています。  
私が思いついた方法は以下の2つです。

  1. [JsonDocument](https://docs.microsoft.com/en-us/dotnet/api/system.text.json.jsondocument?view=netcore-3.0)でネストを深掘りする方法

  * サンプルコードの`ParseElephant`メソッドです

  2. [JsonExtensionDataAttribute](https://docs.microsoft.com/en-us/dotnet/api/system.text.json.serialization.jsonextensiondataattribute?view=netcore-3.0)である程度動的に値を取得する方法

  * サンプルコードの`ParseClass`メソッドです

Json内部で入れ子になっているJsonを[JsonConverter](https://docs.microsoft.com/en-
us/dotnet/api/system.text.json.serialization.jsonconverter?view=netcore-3.0)でカスタム変換できれば良かったのですが、動的な名称の項目に適用する方法が見つかりませんでした。

**サンプルコード**

[code]

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text.Json;
    using System.Text.Json.Serialization;
    
    namespace ConsoleApp1
    {
        class Program
        {
            static void Main(string[] args)
            {
                ParseElephant();
                ParseClass();
            }
    
            /// <summary>
            /// JsonDocumentを使いながらJsonを深掘りしてプロパティを補完する手法
            /// </summary>
            private static void ParseElephant()
            {
                // objData 以外をざっくり作成
                Root root = JsonSerializer.Deserialize<Root>(JSON_TEXT);
                var doc = JsonDocument.Parse(JSON_TEXT);
                root.objData = doc.RootElement.EnumerateObject()
                    .Where(x => x.Name.StartsWith("objData"))     // Jsonキーが "objData" から始まるもの
                    .Select(elm =>
                    {
                        // Rootと同様に hogeobj 以外をざっくり作成
                        ObjDatum objDatum = JsonSerializer.Deserialize<ObjDatum>(elm.Value.GetRawText());
                        // 上のLinqと同様の深掘り手法
                        using var hogeDoc = JsonDocument.Parse(elm.Value.GetRawText());
                        var prop = hogeDoc.RootElement.EnumerateObject()
                                    .First(x => x.Name.StartsWith("hogeobj"));    // Jsonキーが "hogeobj" から始まるもの
                        objDatum.hogeobj = JsonSerializer.Deserialize<Hogeobj>(prop.Value.GetRawText());
                        return objDatum;
                    })
                    .ToList();
                Console.WriteLine(JsonSerializer.Serialize<Root>(root));
            }
    
            /// <summary>
            /// クラスメソッドからJsonSerializerを呼び出してプロパティを補完する手法
            /// </summary>
            private static void ParseClass()
            {
                using var doc = JsonDocument.Parse(JSON_TEXT);
                var root = JsonSerializer.Deserialize<Root2>(JSON_TEXT);
                //プロパティは正しく取れる
                Console.WriteLine(root.ObjData[0].hogeobj.hoge);
                //シリアライズすると求めるフォーマットから離れる
                Console.WriteLine(JsonSerializer.Serialize<Root2>(root));
            }
    
            const string JSON_TEXT = @"{
        ""textData"": ""textVal"",
        ""numData"" : 123,
        ""objData1"": {
            ""hogeText"": ""hogeTextVal1"",
            ""hogeNum"" : 1,
            ""hogeobj1"": {
                ""hoge"": ""hogeVal1"",
                ""hage"": ""hageVal1"" 
            }
        },
        ""objData2"": {
            ""hogeText"": ""hogeTextVal2"",
            ""hogeNum"" : 2,
            ""hogeobj2"": {
                ""hoge"": ""hogeVal2"",
                ""hage"": ""hageVal2"" 
            }
        },
        ""objData3"": {
            ""hogeText"": ""hogeTextVal3"",
            ""hogeNum"" : 3,
            ""hogeobj3"": {
                ""hoge"": ""hogeVal3"",
                ""hage"": ""hageVal3"" 
            }
        }
    }";
        }
    
        public class Root
        {
            public string textData { get; set; }
    
            public long numData { get; set; }
    
            public List<ObjDatum> objData { get; set; }
        }
    
        public class ObjDatum
        {
            public string hogeText { get; set; }
    
            public long hogeNum { get; set; }
    
            public Hogeobj hogeobj { get; set; }
        }
    
        public class Hogeobj
        {
            public string hoge { get; set; }
    
            public string hage { get; set; }
        }
    
        public class Root2
        {
            [JsonPropertyName("textData")]
            public string TextData { get; set; }
    
            [JsonPropertyName("numData")]
            public long NumData { get; set; }
    
            [JsonIgnoreAttribute]
            public List<ObjDatum2> ObjData
            {
                get
                {
                    return ExtensionData.Values
                      .Select(v => JsonSerializer.Deserialize<ObjDatum2>(v.GetRawText())).ToList();
                }
            }
    
            [JsonExtensionDataAttribute]
            public IDictionary<string, JsonElement> ExtensionData { get; set; }
        }
    
        public class ObjDatum2
        {
            public string hogeText { get; set; }
    
            public long hogeNum { get; set; }
    
            [JsonIgnoreAttribute]
            public Hogeobj hogeobj
            {
                get
                {
                    return JsonSerializer.Deserialize<Hogeobj>(ExtensionData.Values.First().GetRawText());
                }
            }
    
            [JsonExtensionDataAttribute]
            public IDictionary<string, JsonElement> ExtensionData { get; set; }
        }
    }
    
[/code]

**出力結果**

[code]

    {"textData":"textVal","numData":123,"objData":[{"hogeText":"hogeTextVal1","hogeNum":1,"hogeobj":{"hoge":"hogeVal1","hage":"hageVal1"}},{"hogeText":"hogeTextVal2","hogeNum":2,"hogeobj":{"hoge":"hogeVal2","hage":"hageVal2"}},{"hogeText":"hogeTextVal3","hogeNum":3,"hogeobj":{"hoge":"hogeVal3","hage":"hageVal3"}}]}
    hogeVal1
    {"textData":"textVal","numData":123,"objData1":{"hogeText":"hogeTextVal1","hogeNum":1,"hogeobj1":{"hoge":"hogeVal1","hage":"hageVal1"}},"objData2":{"hogeText":"hogeTextVal2","hogeNum":2,"hogeobj2":{"hoge":"hogeVal2","hage":"hageVal2"}},"objData3":{"hogeText":"hogeTextVal3","hogeNum":3,"hogeobj3":{"hoge":"hogeVal3","hage":"hageVal3"}}}
    
[/code]

前者の`ParseElephant`は冗長ですが、再度Jsonにシリアライズした時にobjDataはきれいな配列になります。  
後者の`ParseClass`の方が一見コードはきれいに見えますし、プロパティの値は問題なく取得できますが、Jsonにシリアライズすると動的なキー名称はそのままです。


CC BY-SA 4.0
2020-06-29T11:23:47.240
68113
2020-06-29T23:41:41.773
2020-06-29T23:41:41.773
9820
9820
68103
answer


payanecoさんもおっしゃっている通りスッキリした書き方はできませんね。

[オーバーフロー JSON の処理](https://docs.microsoft.com/ja-
jp/dotnet/standard/serialization/system-text-json-how-to#handle-overflow-
json)という機能を使うと対応するプロパティのない要素を`Dictionary`として取得することができます。これを利用して、次のようにクラス定義します。

[code]

    public class Root{
        public string textData { get; set; }
        public long numData { get; set; }
    
        [JsonExtensionData]
        public Dictionary<string, JsonElement> ExtensionData { private get; set; }
    
        public Dictionary<string, ObjDatum> objData
            => ExtensionData.ToDictionary(p => p.Key, p => JsonSerializer.Deserialize<ObjDatum>(p.Value.GetRawText()));
    }
    
    public class ObjDatum {
        public string hogeText { get; set; }
        public long hogeNum { get; set; }
        public Hogeobj hogeobj { get; set; }
    }
    
    public class Hogeobj {
        public string hoge { get; set; }
        public string hage { get; set; }
    }
    
[/code]

`ExtensionData` という不要なプロパティが見えてしまいますが、 `objData` プロパティを参照すると再度デシリアライズして
`Dictionary<string, ObjDatum>` を得ることができます。


CC BY-SA 4.0
2020-06-29T23:12:21.127
68122
2020-06-29T23:12:21.127
4236
68103
answer