【问题标题】:Picking Out Simple Properties from Hierarchical JSON从分层 JSON 中挑选简单属性
【发布时间】:2016-07-24 01:14:29
【问题描述】:

* 尽管其他用户对我的标题进行了编辑,但我正在寻找一种使用来自 C# 的 JSON.NET 库的解决方案 *

包含伪代码的回复很好! :)

我正在尝试使用 JSON 数据集提供的分层数据。我正在使用 C# 和 JSON.NET。如果有帮助的话,我愿意将 Linq 一般用于 JSON.NET,尤其是 Linq;否则,使用非 Linq C#/JSON.NET 就可以了。

理想情况下,我试图优雅地完成两件事:

  1. 我想提取表示每个分支和该分支自身属性的 JSON,而不是它的子(嵌套)分支对象(稍后我会详细解释)。

  2. 我想在创建分支对象时跟踪父节点。

如需进一步考虑,请参考以下 JSON 摘录:

{
  "Branch1": {
    "Prop1A" : "1A",
    "Prop1B" : "1B",
    "Prop1C" : "1C",
    "Branch2" : {
      "Prop2A" : "2A",
      "Prop2B" : "2B",
      "Prop2C" : "2C",
      "Branch3" : {
        "Prop3A" : "3A",
        "Prop3B" : "3B",
        "Prop3C" : "3C"
      }
    }
  }
}

与目标 1 相关(上图): 给定由嵌套 JSON 对象组成的 JSON,我只想为每个分支挑选简单的(字符串)属性。例如,我想提取仅包含 Prop1A、Prop1B 和 Prop1C 属性的 Branch1 的 JSON。然后我想提取仅包含 Prop2A、Prop2B 和 Prop2C 属性等的 Branch2 的 JSON。我意识到我可以将整个 JSON 表示为 JSON.NET JToken 对象,然后遍历它的 Children() 并只看对于 JTokenType.Property 类型,但也许有一种更优雅的方法可以使用 Linq 快速挑选出属性类型......?最后,我将拥有三个独立的 JSON 对象,如下所示:

JSON 对象 1:

{
  "Prop1A" : "1A",
  "Prop1B" : "1B",
  "Prop1C" : "1C"
}

JSON 对象 2:

{
  "Prop2A" : "2A",
  "Prop2B" : "2B",
  "Prop2C" : "2C"
}

JSON 对象 3:

{ “Prop3A”:“3A”, “Prop3B”:“3B”, “Prop3C”:“3C” }

与目标 2 相关(上图): 理想情况下,上面提取的每个 JSON 也将具有指示其父级的属性。因此,最终的 JSON 对象看起来像这样:

{
  "Prop1A" : "1A",
  "Prop1B" : "1B",
  "Prop1C" : "1C",
  "Parent" : ""
}

还有:

{
  "Prop2A" : "2A",
  "Prop2B" : "2B",
  "Prop2C" : "2C",
  "Parent" : "Branch1"
}

还有:

{
  "Prop3A" : "3A",
  "Prop3B" : "3B",
  "Prop3C" : "3C",
  "Parent" : "Branch2"
}

有什么想法吗?

【问题讨论】:

  • 为什么 "Branch1" 不是第一个 "Prop1A" 对象的父对象?毕竟那是直接的父属性名称。
  • 因为本例中的 JSON 表示 JSON 对象的父子层次关系。在这种情况下,对象 1 在对象 2 的父对象中,即对象 3 的父对象。对象 1 映射到 Branch1(一个没有父对象但有一个子分支的 JSON 对象--Branch 2);对象 2 映射到 Branch2(一个 JSON 对象,Branch1 作为其父级,并且有一个子分支--Branch 3);对象 3 映射到 Branch3(一个以 Branch2 作为其父分支且没有子分支的 JSON 对象)。

标签: c# json.net


【解决方案1】:

您可以使用 JContainer.DescendantsAndSelf() 查找 JSON 层次结构中的所有对象,然后对于每个对象,循环遍历其 properties 并过滤掉那些 valueJValue 原语的对象。因此,以下查询会创建一个 List<JObject>,其中包含您需要的属性名称和值:

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

var query1 = from o in root.DescendantsAndSelf().OfType<JObject>()      // Find objects
             let l = o.Properties().Where(p => p.Value is JValue)       // Select their primitive properties
             where l.Any()                                              // Skip objects with no properties
             select new JObject(l);                                     // And return a JObject

var list1 = query1.ToList();

要始终跳过根对象,即使它具有原始属性,请使用JContainer.Descendants()。如果你真的只想要字符串值属性(而不是原始属性),你可以检查JToken.Type 属性:

             let l = o.Properties().Where(p => p.Value.Type == JTokenType.String)       // Select their string-valued properties

可以增强查询以包含一个合成的“父”属性,该属性给出包含该对象的直接父属性的名称,使用JToken.Ancestors

var query2 = from o in root.DescendantsAndSelf().OfType<JObject>()      // Find objects
             let l = o.Properties().Where(p => p.Value is JValue)       // Select their primitive properties
             where l.Any()                                              // Skip objects with no properties
             // Add synthetic "Parent" property
             let l2 = l.Concat(new[] { new JProperty("Parent", o.Ancestors().OfType<JProperty>().Select(a => a.Name).FirstOrDefault() ?? "") })
             select new JObject(l2);                                    // And return a JObject.

var list2 = query2.ToList();

但是,在您想要的输出中,您似乎想要对象父级的属性名称,而不是对象的属性名称。如果是这样,你可以这样做:

var query3 = from o in root.DescendantsAndSelf().OfType<JObject>()      // Find objects
             let l = o.Properties().Where(p => p.Value is JValue)       // Select their primitive properties
             where l.Any()                                              // Skip objects with no properties
             // Add synthetic "Parent" property
             let l2 = l.Concat(new[] { new JProperty("Parent", o.Ancestors().OfType<JProperty>().Skip(1).Select(a => a.Name).FirstOrDefault() ?? "") })
             select new JObject(l2);                                    // And return a JObject.

var list3 = query3.ToList();

对于最终查询,如果我这样做:

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

生成以下输出,显示JObject 列表包含您需要的内容:

[
  {
    "Prop1A": "1A",
    "Prop1B": "1B",
    "Prop1C": "1C",
    "Parent": ""
  },
  {
    "Prop2A": "2A",
    "Prop2B": "2B",
    "Prop2C": "2C",
    "Parent": "Branch1"
  },
  {
    "Prop3A": "3A",
    "Prop3B": "3B",
    "Prop3C": "3C",
    "Parent": "Branch2"
  }
]

请注意,如果 JSON 对象本身具有名为 "Parent" 的属性,则 JObject 构造函数可能会引发重复键异常。

【讨论】:

  • 赞赏——但需要与算法不同/分离的 JSON 对象,而不是尝试重构现有 JSON,而是尝试将原始 JSON 分解为多个较小的 JSON 对象,这些对象通过指向父级的链接保留层次结构。请参阅对原始帖子的说明。抱歉,可能没有使请求更清晰。另外,喜欢 DescendantsAndSelf()。非常感谢。
  • 我不知道您所说的 通过指向父级的链接保留层次结构是什么意思。 Linq-to-JSON 中的父/子关系是双向的,所以如果子“链接”到父级,则父级必须具有引用子级的属性,这不是您在问题中显示的。另外,您是否只是想获取 JObject 对象列表而不是字典列表?
  • Sure-fair q's:最终,这些 JSON 对象将最终成为 C# 对象。 Branch1 将是一个表示 Windows GUI 控件容器(例如表单控件)的对象; Branch2 将是窗体内部的容器对象(例如面板); Branch3 将是面板内的控件。我只知道控件的父级就足够了。 JObject 对象的列表会很好,其中 [0] 有 Branch1,[1] 有 Branch 2,等等。谢谢。
  • @Jazimov - 答案已修改为返回 JObjects 列表
  • 太棒了!不能感谢你!如果可以的话,请提供除在线 JSON.NET 文档之外的任何参考来掌握 JSON.NET 的 Linq,我觉得这些文档很难使用/理解...
猜你喜欢
  • 1970-01-01
  • 2022-10-05
  • 2020-04-07
  • 1970-01-01
  • 2021-04-15
  • 2015-03-16
  • 2017-06-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多