使用JQ从JSON中选择特定的、任意嵌套的对象

8
我正在寻找高效的方法来搜索一个大的JSON对象,以查找符合筛选条件的“子对象”(我想通过 select()来实现)。然而,顶层JSON是一个包含任意嵌套的对象,包括更简单的值、对象和对象数组。例如:
{
  "name": "foo",
  "class": "system",
  "description": "top-level-thing",
  "configuration": {
    "status": "normal",
    "uuid": "id"
  },
  "children": [
    {
      "id": "c1",
      "class": "c1",
      "children": [
        {
          "id": "c1.1",
          "class": "c1.1"
        },
        {
          "id": "c1.1",
          "class": "FINDME"
        }
      ]
    },
    {
      "id": "c2",
      "class": "FINDME"
    }
  ],
  "thing": {
    "id": "c3",
    "class": "FINDME"
  }
}    

我有一个解决方案,可以部分地实现我想要的(并且易于理解):

jq -r '.. | arrays | .[] | select(.class=="FINDME"?) | .id'

它返回:

c2
c1.1

...然而,它错过了c3,并且改变了输出项的顺序。此外,我希望这个解决方案能够适用于潜在的非常大的JSON结构,我希望确保找到一个高效的解决方案。如果有什么东西可以让jq新手(包括我自己)更容易理解,那就更好了。

顺便说一下,以下是我在路上使用的参考资料,以防对其他人有所帮助:

2个回答

8

如果输入的JSON数据较小或适中大小,您可以使用..,但似乎您希望选择对象,就像这样:

.. | objects | select(.class=="FINDME"?) | .id

对于非常大的JSON文档,这可能需要太多的内存,因此了解jq的流式解析器可能很值得。不幸的是,它更难使用,因此我建议首先尝试上述方法,如果您感兴趣,可以在通常的文档中查找有关--stream选项的说明。

嗯,那应该很明显 - 我会使用这个选项,直到遇到性能问题。 - crimson-egret

3

这里是一种流式解析器的解决方案。要理解它,您需要阅读有关--stream选项的介绍,但关键在于输出包括以下形式的行:[路径,值]

program.jq

foreach inputs as $in (null;
  if has("id") and has("class") then null
  else . as $x
  | $in
  | if length != 2 then null
    elif .[0][-1] == "id" then ($x + {id: .[-1]})
    elif .[0][-1] == "class"
         and .[-1] == "FINDME" then  ($x + {class: .[-1]})
    else $x
    end
  end;
  select(has("id") and has("class")) | .id )

调用

jq -n --stream -f program.jq input.json

样例输入输出

"c1.1"
"c2"
"c3"

虽然不如你给出的另一个答案易读,但它实现了我想要的功能,包括保留顺序,而且我会从中学到一些东西。谢谢。 - crimson-egret
请注意更新以消除假设。您能否发布一些有关文件大小和比较时间的详细信息? - peak
感谢您进行假设移除更新。由于输出可能与我的示例略有不同,因此该部分非常有帮助。至于时间安排,我还没有真实的数据集,所以无法提供。我可能会生成一些模拟数据集,如果我这样做了,那么我将在那时发布一个比较。 - crimson-egret

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接