在 jq 中转置对象

7

我不确定“transpose”是否是正确的术语,但我想使用jq来转置一个二维对象,例如这样:

[
    {
        "name": "A",
        "keys": ["k1", "k2", "k3"]
    },
    {
        "name": "B",
        "keys": ["k2", "k3", "k4"]
    }
]

我希望将其转换为:

{
    "k1": ["A"],
    "k2": ["A", "B"],
    "k3": ["A", "B"],
    "k4": ["A"],
}

我可以使用.[] | {key: .keys[], name}来拆分对象,以获取键和名称的列表,或者我可以使用.[] | {(.keys[]): [.name]}来获取键值对的集合{"k1": ["A"]}等等,但是我不确定最终的连接步骤是否正确。这两种方法都是正确的方向吗?是否有更好的方法?
3个回答

9

这应该可以正常工作:

map({ name, key: .keys[] })
    | group_by(.key)
    | map({ key: .[0].key, value: map(.name) })
    | from_entries

基本方法是将每个对象转换为名称/键对,按键重新分组,然后将它们映射到对象的条目中。
这将产生以下输出:
{
  "k1": [ "A" ],
  "k2": [ "A", "B" ],
  "k3": [ "A", "B" ],
  "k4": [ "B" ]
}

谢谢!我已经使用group_by了,但是我必须承认后面的嵌套map还是有点让我困惑。是否有更简单的示例或文档来解释这种行为? - cmbuckley
当你执行group_by时,它会将所有具有匹配键的项放入一个数组中。因此,该内部数组中的每个项都将具有相同的键值。所以在那个时候的目标是将数组的数组转换为对象的数组。我们希望value属性是在该数组中找到的名称,因此使用了内部映射。 - Jeff Mercado
这个步骤让我感到困惑,具体是这一步。我把它看作是嵌套的foreach`,我想这就是我的问题所在 :-) - cmbuckley
1
要理解“特定的这一步”,需要将其分解一下,看看:.[0] | {key: .[0].key,value:map(.name)} - peak
那很有帮助,现在看起来相当明显!不确定为什么昨天没有理解。 - cmbuckley
显示剩余2条评论

1
这里有一个简单的解决方案,可能更易于理解。它基于这样一个想法:可以通过添加有关其他(键 -> 值)对的详细信息来扩展字典(JSON对象)。
# input: a dictionary to be extended by key -> value 
# for each key in keys
def extend_dictionary(keys; value):
  reduce keys[] as $key (.; .[$key] += [value]);

reduce .[] as $o ({}; extend_dictionary($o.keys; $o.name) )


$ jq -c -f transpose-object.jq input.json
{"k1":["A"],"k2":["A","B"],"k3":["A","B"],"k4":["B"]}

这个例子对于命令式程序员来说肯定更易懂!然而,我很想学习一些更高级的数据驱动技术,感觉更像是“惯用的jq” :-) - cmbuckley
“map”和“reduce”就像“阴”和“阳”一样,所以我不确定为什么您认为其中一个比另一个“更高级”。 jq非常好地采用了map/reduce范式,因此我很困惑为什么您认为其中一个比另一个更“符合习惯”。这是因为jq的reduce语法不采用函数调用形式吗? - peak
1
我理解map/reduce作为一种范式,并且欣赏这两种解决方案之间的区别,但我的意思是这感觉更像一种过程化的方法。现在我看到上面嵌套的map正在执行与此处嵌套的reduce相同的操作,我不太倾向于将其称为高级,但我确实喜欢它的简洁! - cmbuckley

0

如果“name”所有值都不同,这里有一个更好的解决方案。它更好是因为它使用了完全通用的过滤器invertMapping;也就是说,invertMapping可以是内置或库函数。借助于这个函数,解决方案变成了一个简单的三行代码。

此外,如果“name”的值不都是唯一的,那么下面的解决方案可以通过修改输入的初始减少(即在调用invertMapping之前的那一行)来轻松地进行调整。

# input: a JSON object of (key, values) pairs, in which "values" is an array of strings; 
# output: a JSON object representing the inverse relation
def invertMapping: 
  reduce to_entries[] as $pair
    ({}; reduce $pair.value[] as $v (.; .[$v] += [$pair.key] ));


map( { (.name) : .keys} )
| add
| invertMapping

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