JSON.NET:在Linq查询中将JArray展平为JObject

时间:2016-09-23 16:16:56

标签: linq json.net

这是我之前发布的帖子的后续内容,在这里: Querying and Filtering Array of JObjects with Linq

我在这里发布了一个dotnetfiddle: https://dotnetfiddle.net/s75wGu

道歉,但我无法弄清楚为什么我在dotnetfiddle中的System.Collections.Generic.IEnumerable上收到错误,但该程序在Linqpad和Visual Studio下工作。

源JSON如下所示:

{
  "Object": {
    "NM": "Name1",
    "AA": "Val01",
    "BB": "Val02",
    "CC": "Val03",
    "DD": "Val04",
    "EE": "Val05",
    "FF": "Val06",
    "GG": "Val07",
    "HH": "Val08",
    "Object": [
      {
        "NM": "Name2",
        "AA": "Val01",
        "BB": "Val02",
        "CC": "Val03",
        "DD": "Val04",
        "EE": "Val05",
        "FF": "Val06",
        "GG": "Val07",
        "HH": "Val08",
        "Object": [
          {
            "NM": "Name3",
            "AA": "Val01",
            "BB": "Val02",
            "CC": "Val03",
            "DD": "Val04",
            "EE": "Val05",
            "FF": "Val06",
            "GG": "Val07",
            "HH": "Val08"
          },
          {
            "NM": "Name4",
            "AA": "Val01",
            "BB": "Val02",
            "CC": "Val03",
            "DD": "Val04",
            "EE": "Val05",
            "FF": "Val06",
            "GG": "Val07",
            "HH": "Val08"
          },
          {
            "NM": "Name5",
            "AA": "Val01",
            "BB": "Val02",
            "CC": "Val03",
            "DD": "Val04",
            "EE": "Val05",
            "FF": "Val06",
            "GG": "Val07",
            "HH": "Val08"
          }
        ]
      }
    ]
  }
}

如您所见,代码旨在查询JSON文档并将其展平为List<JObject>对象。

if (jo is JObject)子句应该是直截了当的:如果Linq迭代找到一个JObject对象,它会创建一个joWork JObject实例,向其添加属性,并返回单个joWork对象。这很好。

但是我的问题是使用if (jo is JArray)子句:在这里,我认为Linq希望我返回一个JObject对象,但是我有一个JObject数组,我想作为单独的JObject返回。

我的dotnetfiddle代码产生的输出是:

joWork-Object: {
  "_Parent": "",
  "NM": "Name1",
  "AA": "Val01",
  "BB": "Val02",
  "CC": "Val03",
  "DD": "Val04",
  "EE": "Val05",
  "FF": "Val06",
  "GG": "Val07",
  "HH": "Val08"
}
joWork-Array: {
  "_Parent": "Name1",
  "_Group": 1,
  "NM": "Name2",
  "AA": "Val01",
  "BB": "Val02",
  "CC": "Val03",
  "DD": "Val04",
  "EE": "Val05",
  "FF": "Val06",
  "GG": "Val07",
  "HH": "Val08"
}
joWork: 
joWork-Array: {
  "_Parent": "Name2",
  "_Group": 2,
  "NM": "Name5",
  "AA": "Val01",
  "BB": "Val02",
  "CC": "Val03",
  "DD": "Val04",
  "EE": "Val05",
  "FF": "Val06",
  "GG": "Val07",
  "HH": "Val08"
}
joWork: 
joWork: 
joWork: 

请注意,我们只有对象名称(NM)Name1,Name2,然后是Name5。我想要的是Name1,Name2,Name3,Name4和Name5--每个都有一个包含父对象的NM属性的_Parent属性。

我意识到我只获得Name5,因为我的Linq查询中的JArray if语句只返回数组中的最后一个元素。我想返回数组中的所有元素。最终输出应如下所示:

joWork-Object: {
  "_Parent": "",
  "NM": "Name1",
  "AA": "Val01",
  "BB": "Val02",
  "CC": "Val03",
  "DD": "Val04",
  "EE": "Val05",
  "FF": "Val06",
  "GG": "Val07",
  "HH": "Val08"
}
joWork-Array: {
  "_Parent": "Name1",
  "_Group": 1,
  "NM": "Name2",
  "AA": "Val01",
  "BB": "Val02",
  "CC": "Val03",
  "DD": "Val04",
  "EE": "Val05",
  "FF": "Val06",
  "GG": "Val07",
  "HH": "Val08"
}
joWork-Array: {
  "_Parent": "Name2",
  "_Group": 2,
  "NM": "Name3",
  "AA": "Val01",
  "BB": "Val02",
  "CC": "Val03",
  "DD": "Val04",
  "EE": "Val05",
  "FF": "Val06",
  "GG": "Val07",
  "HH": "Val08"
}
joWork-Array: {
  "_Parent": "Name2",
  "_Group": 2,
  "NM": "Name4",
  "AA": "Val01",
  "BB": "Val02",
  "CC": "Val03",
  "DD": "Val04",
  "EE": "Val05",
  "FF": "Val06",
  "GG": "Val07",
  "HH": "Val08"
}
joWork-Array: {
  "_Parent": "Name2",
  "_Group": 2,
  "NM": "Name5",
  "AA": "Val01",
  "BB": "Val02",
  "CC": "Val03",
  "DD": "Val04",
  "EE": "Val05",
  "FF": "Val06",
  "GG": "Val07",
  "HH": "Val08"
}

1 个答案:

答案 0 :(得分:1)

您可以使用Enumerable.SelectMany()代替Enumerable.Select()来返回linq查询中的多个对象,将返回的内部枚举展平为整个枚举的组成对象:

        var root = (JContainer)JToken.Parse(jsonFromFile);

        var controlRoot = (JContainer)root["Object"];

        string parentKey = "";
        int groupId = 0;

        var query =
            controlRoot
            .DescendantsAndSelf()
            .Where(jt => (jt.Type == JTokenType.Object) || (jt.Type == JTokenType.Array))
            .SelectMany(jo =>
            {
                if (jo.Parent is JProperty)
                {
                    var ParentName = ((JProperty)jo.Parent).Ancestors()
                                    .Where(jt => jt.Type == JTokenType.Property)
                                    .Select(jt => ((JProperty)jt).Name.ToString())
                                    .FirstOrDefault();

                    if (ParentName == "Object")
                    {
                        parentKey = ((JProperty)jo.Parent).AncestorsAndSelf()   // Climb up the json container parent/child hierachy
                           .Select(p => p.SelectToken("NM"))                    // Get the "parentKey" property in the current parent (if present)
                           .FirstOrDefault(k => k != null).ToString();          // Return the first one found.
                    }

                    if (jo is JObject)
                    {
                        // add a property for the parent
                        var joWork = new JObject(new JProperty("_Parent", parentKey));

                        // add only the string properties in the current object
                        joWork.Add(((JObject)jo).Properties()
                            .Where(p => p.Value.Type == JTokenType.String));

                        return new[] { joWork };
                    }
                    else if (jo is JArray)
                    {
                        groupId++;
                        return from o in jo
                               let arrayItems = ((JObject)o).Properties().Where(p => p.Value.Type == JTokenType.String).ToList()
                               where arrayItems.Count > 0
                               select new JObject(new[]
                                   {
                                       new JProperty("_Parent", parentKey),
                                       new JProperty("_Group", groupId),
                                   }
                                   .Concat(arrayItems));
                    }
                }
                return Enumerable.Empty<JObject>();
            }
            )
            .ToList();

        Console.WriteLine(JsonConvert.SerializeObject(query, Formatting.Indented));

这里我在SelectMany()方法中使用LINQ查询语法,因为它更紧凑。替代方法语法是:

                    else if (jo is JArray)
                    {
                        groupId++;
                        return jo.Select(o =>
                            {
                                var arrayItems = ((JObject)o).Properties()
                                    .Where(p => p.Value.Type == JTokenType.String).ToList();
                                if (arrayItems.Count > 0)
                                {
                                    var joWork = new JObject();

                                    // add a property for the parent
                                    joWork.Add(new JProperty("_Parent", parentKey));
                                    joWork.Add(new JProperty("_Group", groupId));

                                    // add only the string properties in the current object
                                    joWork.Add(arrayItems);
                                    return joWork;
                                }
                                else
                                {
                                    return null;
                                }
                            })
                            .Where(o => o != null);
                    }