【问题标题】:Getting only desired properties from nested array values with jq使用 jq 从嵌套数组值中仅获取所需的属性
【发布时间】:2018-01-27 06:10:26
【问题描述】:

我最终想要的结构是:

{
  "catalog": [
    {
      "name": "X",
      "catalog": [
        { "name": "Y", "uniqueId": "Z" },
        { "name": "Q", "uniqueId": "B" }
      ]
    }
  ]
}

这就是现有结构的样子,只是每个级别都有 许多 其他属性 (https://gist.github.com/ajcrites/e0e0ca4ca3a08ff2dc401ec872e6094c)。我只是想过滤掉那些并得到一个看起来特别像这样的 JSON 格式。

我开始使用:jq '.catalog',但这仅返回数组。我仍然想要那里的catalog 属性名称。我可以用jq '{catalog: .catalog[]} 做到这一点,但这会单独打印出每个目录对象,这会使整个输出无效的 JSON。我仍然希望属性在数组中。有没有办法使用 jq 过滤数组中的特定属性键值?

【问题讨论】:

  • 显示当前输入所需的输出。此外,输入不是有效的 json:它不能在数组中包含 "name": "Y" 这样的符号
  • @RomanPerekhrest 是所需的输出。输入是一样的,只是有更多的属性。我在问题中包含了一个示例
  • 我有一个提供预期输出的解决方案。但我可以称其为“黑客”,而不是通用方法。这就是我没有发布它的原因

标签: json field filtering jq


【解决方案1】:

以下将给定的输入转换为所需的输出,这很可能是您想要的:

{catalog}
| .catalog |= map( {name, catalog} )
| .catalog[].catalog |= map( {name, uniqueId} )
| .catalog |= .[0:1]

但是,我不清楚这是否真的是您想要的,因为您没有讨论给定 JSON 输入中的重复。所以也许你真的不想要上面的最后一行,或者你希望以其他方式处理重复项,或者......

无论如何,在这里保持简单的诀窍是使用|=

另一种方法是使用 del 删除不需要的属性(而不是选择您想要的属性),但在目前的情况下,这将(充其量)乏味。

【讨论】:

  • 你能稍微解释一下|=的作用吗?
  • .a |= X 就像赋值:.a = (.a | X)。有关详细信息,请参阅手册。您可能还想使用jqplay.org
  • 这给了我我需要的东西;抱歉有点不清楚。实际上|= map() 是我最需要的,你的解释也很有意义
【解决方案2】:

您可以先使用 tostream 转换您的sample.json 进入 [path, value] 数组流,正如您通过运行所看到的那样

jq -c tostream sample.json

这会生成

[["catalog",0,"catalog",0,"name"],"Y"]
[["catalog",0,"catalog",0,"prop11"],""]
[["catalog",0,"catalog",0,"uniqueId"],"Z"]
[["catalog",0,"catalog",0,"uniqueId"]]
[["catalog",0,"catalog",1,"name"],"Y"]
[["catalog",0,"catalog",1,"prop11"],""]
...

reducesetpath 可用于转换回 带有过滤器的原始表单,例如:

reduce (tostream|select(length==2)) as [$p,$v] (
  {};
  setpath($p;$v)
)

添加条件可以轻松省略任何级别的属性。 例如以下删除以“prop”开头的叶子属性:

reduce (tostream|select(length==2)) as [$p,$v] (
  {};
  if $p[-1]|startswith("prop")
  then .
  else setpath($p;$v)
  end
)

使用您的sample.json 这会产生

{
  "catalog": [
    {
      "catalog": [
        {
          "name": "Y",
          "uniqueId": "Z"
        },
        {
          "name": "Y",
          "uniqueId": "Z"
        }
      ],
      "name": "X"
    },
    {
      "catalog": [
        {
          "name": "Y",
          "uniqueId": "Z"
        },
        {
          "name": "Y",
          "uniqueId": "Z"
        }
      ],
      "name": "X"
    }
  ]
}

【讨论】:

    【解决方案3】:

    如果目标是删除某些属性,那么可以使用walk/1 来实现。例如,要删除名称以“prop”开头的属性:

     walk(if type == "object" 
          then with_entries(select(.key|startswith("prop") | not)) 
          else . end)
    

    如果重点是保留某些属性,则同样的方法也适用,例如:

    walk(if type == "object" 
         then with_entries(select(.key == "name" or .key == "uniqueId" or .key == "catalog"))
         else . end)
    

    【讨论】:

      【解决方案4】:

      您可以构建一个文件,其中包含您想要保留的 json(表示为数组)的路径。然后过滤掉不适合这些路径的值。

      paths.json:

      ["catalog","name"]
      ["catalog","catalog","name"]
      ["catalog","catalog","uniqueId"]
      

      然后根据路径过滤值。使用流是一种很好的方法,因为它可以让您直接访问这些路径:

      $ jq --slurpfile paths paths.json '
      def keep_path($path): any($paths[]; . == [$path[] | select(strings)]);
      fromstream(tostream | select(length == 1 or keep_path(.[0])))
      ' input.json
      

      【讨论】:

      • 我尝试在我的解决方案中使用fromstream,但它没有返回任何内容(实际上,过去fromstream 唯一一次为我工作是当我将它与truncate_stream 一起使用时)但我观察到您很小心地包含了我的 reduce select(length == 2) 丢弃的 length == 1 路径。我一直不明白为什么tostream 会生成那些length==1 路径,但我想我现在知道了。
      • 长度为 1 的条目表示相应对象或数组的定义结束。如果没有这些条目,fromstream 将不知道何时关闭相应的值。结果中将省略任何未打开的内容。至少,我是这么理解的。 jq 中的 Streams 对我来说一直是一个很大的问号,我对它的了解还不够,无法有效地使用它。
      • 另外,根据我所看到的流是如何创建的,当根是一个数组时最有意义。否则,流的最后一项对数据的外观没有意义。空对象和数组也很奇怪。我不确定如何解释这些结果。
      猜你喜欢
      • 1970-01-01
      • 2013-10-11
      • 1970-01-01
      • 2020-01-24
      • 1970-01-01
      • 2021-12-19
      • 2019-04-17
      • 2019-05-12
      • 1970-01-01
      相关资源
      最近更新 更多