【问题标题】:Querying JSON Nested Arrays with Linq, JSON.NET, C#使用 Linq、JSON.NET、C# 查询 JSON 嵌套数组
【发布时间】:2016-07-27 20:50:37
【问题描述】:

这篇文章旨在提出与我最近的另一篇文章 (Picking Out Simple Properties from Hierarchical JSON Part II) 相关的更直接的问题:

给定嵌套的 JSON 文档,如下所示:

{
  "Array1": {
    "Array1A": [
      { "Item1": "1" },
      { "Item2": "2" },
      { "Item3": "3" }
    ],
    "Array1B": [
      { "Item1": "1" },
      { "Item2": "2" },
      { "Item3": "3" }
    ]
  },
  "Array2": {
    "Array2A": [
      { "Item1": "1" },
      { "Item2": "2" },
      { "Item3": "3" }
    ]
  },
  "Array3": {
    "Array3A": [
      { "Item1": "1" },
      { "Item2": "2" },
      { "Item3": "3" }
    ],
    "Array3B": [
      { "Item1": "1" },
      { "Item2": "2" },
      {
        "Array3B1": [
          { "Item1": "1" },
          { "Item2": "2" },
          { "Item3": "3" }
        ]
      }
    ],
    "Array3C": [
      { "Item1": "1" },
      { "Item2": "2" },
      { "Item3": "3" }
    ]
  }
}

(注意:以上已使用 JSLint 进行验证。)

请注意,JSON 是动态的——我事先不知道会有多少数组或数组将嵌套多深。

目标: 我的目标是在 List<JObject> 对象中表示每个数组(即 Array1、Array2、Array3、Array3A、Array3B 和 Array3B1)。列表中的每个项目都是 JProperty 对象的集合,其中包含该数组的字符串属性。因为 List 本身并不对分层数据进行建模,所以我需要向每个引用该数组的父项的 List<JObject> 项目添加一个合成属性。因此,Array1 的父级是一个空字符串; Array2 是 Array1,Array3 是 Array2,Array3A 是 Array3,Array3B 是 Array3,Array 3B1 是 Array3B...

问题: 1. 如何使用 C# Linq 创建一个如下所示的List<JObject> 对象:

list[0]: 
{"Name":"Array1","Parent":""}

list[1]:
{"Name":"Array1A","Item1":"1","Item2":"2","Item3":"3","Parent":"Array1"}

list[2]:
{"Name":"Array1B","Item1":"1","Item2":"2","Item3":"3","Parent":"Array1"}

list[3]:
{"Name":"Array2","Parent":""}

list[4]:
{"Name":"Array2A","Item1":"1","Item2":"2","Item3":"3","Parent":"Array2"}

list[5]:
{"Name":"Array3","Parent":""}

list[6]:
{"Name":"Array3A","Item1":"1","Item2":"2","Item3":"3","Parent":"Array3"}

list[7]:
{"Name":"Array3B","Item1":"1","Item2":"2","Parent":"Array3"}

list[8]:
{"Name":"Array3B1","Item1":"1","Item2":"2","Item3":"3","Parent":"ArrayB"}

list[9]:
{"Name":"Array3C","Item1":"1","Item2":"2","Item3":"3","Parent":"Array3"}

请注意:

  • 每个List<JObject> 只包含字符串属性。
  • 在列表 [7] 中,缺少 Item2 之后的 JSON 令牌,因为它是一个数组。相反,该项目在 list[8] 中用正确的父引用表示。

【问题讨论】:

    标签: c# linq json.net


    【解决方案1】:

    这样的事情怎么样:

    List<JObject> list = 
        JObject.Parse(json)
               .Descendants()
               .Where(jt => jt.Type == JTokenType.Property && ((JProperty)jt).Value.HasValues)
               .Cast<JProperty>()
               .Select(prop =>
               {
                   var obj = new JObject(new JProperty("Name", prop.Name));
                   if (prop.Value.Type == JTokenType.Array)
                   {
                       var items = prop.Value.Children<JObject>()
                                             .SelectMany(jo => jo.Properties())
                                             .Where(jp => jp.Value.Type == JTokenType.String);
                       obj.Add(items);
                   }
                   var parentName = prop.Ancestors()
                                        .Where(jt => jt.Type == JTokenType.Property)
                                        .Select(jt => ((JProperty)jt).Name)
                                        .FirstOrDefault();
                   obj.Add("Parent", parentName ?? "");
                   return obj;
               })
               .ToList();
    

    小提琴:https://dotnetfiddle.net/FMxzls

    如果您对LINQ-to-JSON 不是很熟悉,以下是它的分解方式:

    1. 将json字符串解析成JObject

      JObject.Parse(json)
      
    2. 从该 JObject 中,获取其所有后代 JToken

             .Descendants()
      
    3. 将该列表过滤为仅其值具有子级的 JProperties

             .Where(jt => jt.Type == JTokenType.Property && ((JProperty)jt).Value.HasValues)
      
    4. 将 JToken 转换为 JProperties 以使它们在下一步中更易于使用

             .Cast<JProperty>()
      
    5. 现在,对于我们选择的每个 JProperty,将其转换如下:

             .Select(prop =>
             {
      
    6. 创建一个新的 JObject 并将 JProperty 的名称添加为新对象的 Name 属性

                 var obj = new JObject(new JProperty("Name", prop.Name));
      
    7. 如果 JProperty 的值是一个数组...

                 if (prop.Value.Type == JTokenType.Array)
                 {
      
    8. 获取数组的所有直接子对象,即 JObjects

                     var items = prop.Value.Children<JObject>()
      
    9. 从那些 JObject 中,获取所有 JProperties

                                           .SelectMany(jo => jo.Properties())
      
    10. 过滤那些 JProperties 以仅包含值为字符串的那些)

                                           .Where(jp => jp.Value.Type == JTokenType.String);
      
    11. 将这些 JProperties 项添加到我们之前创建的新 JObject 中

                      obj.Add(items);
                  }
      
    12. 接下来,找到当前JProperty的第一个祖先JProperty并得到它的名字

                  var parentName = prop.Ancestors()
                                       .Where(jt => jt.Type == JTokenType.Property)
                                       .Select(jt => ((JProperty)jt).Name)
                                       .FirstOrDefault();
      
    13. 将父名称添加到我们正在构建的 JObject;如果没有父级,则使用空字符串

                  obj.Add("Parent", parentName ?? "");
      
    14. 继续下一个变换

                  return obj;
              })
      
    15. 最后将我们构建的所有 JObject 放入一个列表中。

              .ToList();
      

    【讨论】:

    • 完美运行。我是 Linq 的新手,所以除了您的处理逻辑的有用分解之外,我喜欢学习如何在选择中创建变量、如何使用 SelectMany、如何使用 Ancestors() 以及如何在 Linq 中嵌套查询逻辑。我相信这个答案将证明对许多其他 C#/JSON.NET/Linq 开发人员非常有帮助......做得好!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-29
    • 1970-01-01
    • 2020-09-08
    相关资源
    最近更新 更多