用于对在任何级别找到的所有对象值求和的通用递归 jq

General recursive jq for summing all object values found at any level

我看到 并且想知道 jq 中是否有一种优雅的方法来对一组对象执行一般递归并对它们包含的所有值求和以生成单个输出对象作为所有对象的总和其他对象。

如果在包裹所有其他对象的单个输入对象上更容易执行此操作(以避免吞咽),那也很好。

每个对象都可以是稀疏的,但所有重合的对象键路径应该将它们的值相加到单个输出对象层次结构中的相同位置。

例如(如果这样更容易,围绕这 3 个对象包装一个数组):

{ A { B { x:1, y:4      }, C { x:3,      z:6 }, t:2 }, s:5 }
{ A { B { x:2     , z:3 },                      t:1 }      }
{ A {                    , C {      y:3, z:2 }      }, s:3 }

会产生

{ A { B { x:3, y:4, z:3 }, C { x:3, y:3, z:8 }, t:3 }, s:8 }

TL:DR; 在这些对象的数组上,执行 . as $dot | reduce ([$dot[] | paths(numbers)] | unique)[] as $path ({}; setpath($path; [$dot[] | getpath($path)] | add))


由于您的示例输入无效 JSON,这里是我用于测试的 JSON。如果这与您的不匹配,请告诉我:

[
    {"a": {"b": 1, "c": 2},         "e": 3},
    {"a": {        "c": 2, "d": 3},         "f": 4}
]

首先,我们将了解如何在两个对象之间执行此 "additive merge" 操作;之后,应该很容易在对象数组上执行它。

我们将函数定义为 additive_merge($xs; $ys)。首先,我们将获得每个对象上所有路径的列表,其中有数字,删除重复项:

([$xs, $ys | paths(numbers)] | unique) as $paths

然后,我们将减少这个路径列表,以一个空对象作为初始状态,使用 setpath 将空对象中的每个路径设置为 $xs$ys:

reduce $paths[] as $path ({}; setpath($path; [$xs, $ys | getpath($path)] | add))

现在,我们有了 additive_merge 函数:

def additive_merge($xs;$ys): reduce ([$xs, $ys | paths(numbers)] | unique)[] as $path ({}; setpath($path; [$xs, $ys | getpath($path)] | add));

如果我们想 运行 这个函数遍历一个数组,我们可以通过这个函数缩减数组:

reduce .[] as $x ({}; additive_merge(.; $x))

或者修改函数,使其作用于对象数组,这实际上非常简单;只需使用点输入,将其保存到一个变量中,然后在上一个函数中使用 $xs, $ys 的任何地方解压缩它:

def additive_merge: . as $dot | reduce ([$dot[] | paths(numbers)] | unique)[] as $path ({}; setpath($path; [$dot[] | getpath($path)] | add));

希望对您有所帮助!

这里有一个使用tostreamselectreduce的解决方案, setpathgetpath.

reduce (tostream|select(length==2)|.[0] = .[0][1:]) as [$p,$v] (
   {};
   setpath($p; getpath($p) + $v)
)