使用Linq,JSON.NET,C#查询JSON嵌套数组

时间:2016-07-27 20:50:37

标签: c# linq json.net

此帖旨在提出与我最近的其他帖子(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标记丢失,因为它是一个数组。相反,该项目在列表[8]中用正确的父引用表示。

1 个答案:

答案 0 :(得分:3)

这样的事情怎么样:

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,得到它的所有后代JTokens

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

           .Where(jt => jt.Type == JTokenType.Property && ((JProperty)jt).Value.HasValues)
    
  4. 将JTokens投射到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();