使用jq递归减少数组

recursive reduce arrays using jq

如何递归查找对象中的所有数组并将它们缩减为第一项?

我尝试使用 if .[0]? == "" then .[0] else . end 检测数组,但如果当前对象不是数组,它不会输出任何内容。

输入:

{
  "a": 1,
  "b": [
    1,
    2,
    3
  ],
  "c": [
    {
      "a": 1,
      "b": [
        1,
        2,
        3
      ],
      "c": {
        "a": 1,
        "b": [
          1,
          2,
          3
        ]
      }
    },
    {
      "a": 1,
      "b": [
        1,
        2,
        3
      ],
      "c": {
        "a": 1,
        "b": [
          1,
          2,
          3
        ]
      }
    },
    {
      "a": 1,
      "b": [
        1,
        2,
        3
      ],
      "c": {
        "a": 1,
        "b": [
          1,
          2,
          3
        ]
      }
    }
  ]
}

输出:

{
  "a": 1,
  "b": [
    1
  ],
  "c": [
    {
      "a": 1,
      "b": [
        1
      ],
      "c": {
        "a": 1,
        "b": [
          1
        ]
      }
    }
  ]
}

walk/1 包含在最近 (post-1.5) 的 jq 版本中。它也可以在下面找到。

根据我的理解,它可以用来实现您的 objective:

walk(if type == "array" and length > 1 then [.[0]] else . end)


# Apply f to composite entities recursively, and to atoms
def walk(f):
  . as $in
  | if type == "object" then
      reduce keys[] as $key
        ( {}; . + { ($key):  ($in[$key] | walk(f)) } ) | f
  elif type == "array" then map( walk(f) ) | f
  else f
  end;

peak 的回答很棒。这个问题也可以使用递归来解决,但是如何让它工作还不是很明显。

我想出的天真的解决方案是 (.. | arrays) |= .[0],但这不起作用,因为递归是从外部完成的,这意味着在嵌套情况下我们最终会尝试获取 .[0]来自不再是数组的值。

这可以通过执行 post 顺序遍历来解决,如 in this GitHub issue 所述。

使用 post_recurse,只需将上面的简单解决方案中的 .. 替换为 post_recurse 即可。完整解决方案:

def post_recurse(f): def r: (f | select(. != null) | r), .; r; def post_recurse: post_recurse(.[]?); (post_recurse | arrays) |= .[0]

这是一个使用 tostream 的解决方案,它转换输入对象 进入路径流,过滤掉任何具有非零数组索引的路径 并使用 reducesetpath 将结果转换回对象。所有递归都在 tostream.

内部
[
  tostream

| if   length != 2 then empty
  elif ([.[0][]|numbers|.!=0]|any) then empty
  else .
  end
]

| reduce .[] as $p (
    {};     
    setpath($p[0]; $p[1])
  )