如何通过键将值收集到数组中?

如何通过键将值收集到数组中?

给定条目格式的输入,可能具有重复的键,例如

[
  {"key": "a", "value": 0},
  {"key": "a", "value": 1},
  {"key": "a", "value": 2},
  {"key": "b", "value": 3},
  {"key": "b", "value": 4},
  {"key": "b", "value": 5}
]

我想生产

{"a": [0, 1, 2], "b": [3, 4, 5]}

即,针对每个唯一键将具有相同键的所有值收集在数组中。

我怎样才能做到这一点jq

答案1

使用reduce()in通过将给定值添加到值给定的键来jq逐渐构建结果对象:valuekey

$ jq -c 'reduce .[] as $a ({}; .[$a.key] += [$a.value])' file
{"a":[0,1,2],"b":[3,4,5]}

reduce()行为就像一种循环,在本例中,它迭代.[],即顶级数组中的所有对象。对于每个对象$a,值都会添加到键 下的$a.value结果对象(最初是空对象)中。{}$a.key


对于喜欢使用 的人来说,还有一个提示jq:通过使用debug,您可以在表达式运行时查看表达式中任意点的数据。在这里,我正在研究循环中每次迭代后累加器对象的状态reduce()

$ jq -c 'reduce .[] as $a ({}; .[$a.key] += [$a.value] | debug)' file
["DEBUG:",{"a":[0]}]
["DEBUG:",{"a":[0,1]}]
["DEBUG:",{"a":[0,1,2]}]
["DEBUG:",{"a":[0,1,2],"b":[3]}]
["DEBUG:",{"a":[0,1,2],"b":[3,4]}]
["DEBUG:",{"a":[0,1,2],"b":[3,4,5]}]
{"a":[0,1,2],"b":[3,4,5]}

$a在这里,我正在研究每次迭代中分配的值:

$ jq -c 'reduce (.[]|debug) as $a ({}; .[$a.key] += [$a.value])' file
["DEBUG:",{"key":"a","value":0}]
["DEBUG:",{"key":"a","value":1}]
["DEBUG:",{"key":"a","value":2}]
["DEBUG:",{"key":"b","value":3}]
["DEBUG:",{"key":"b","value":4}]
["DEBUG:",{"key":"b","value":5}]
{"a":[0,1,2],"b":[3,4,5]}

答案2

您可以使用group_by,mapfrom_entries

jq 'group_by(.key) | map({key: .[0].key, value: [.[].value]}) | from_entries' data.json 

答案3

如果您已经了解一种编程语言,那么这是一个简单的选择,而不必使用非常特定的jq语言。和perl

$ perl -MJSON -l -0777 -ne '
    for (@{decode_json$_}) {push @{$out->{$_->{key}}}, $_->{value}}
    print encode_json $out' your-file.json
{"a":[0,1,2],"b":[3,4,5]}

答案4

使用(以前称为 Perl_6)

您需要使用 JSON 解析器:

~$ raku -MJSON::Tiny -e 'my @json = from-json($_).list given slurp;  \
         my %accum.push: .<key> => .<value>.map(*.Num).Slip for @json>>.split(", ");  \
         .say for to-json(%accum.sort);'  file

输入示例:

[
  {"key": "a", "value": 0},
  {"key": "a", "value": 1},
  {"key": "a", "value": 2},
  {"key": "b", "value": 3},
  {"key": "b", "value": 4},
  {"key": "b", "value": 5}
]

示例输出(值已被Num-ified 和Slipped,即展平):

[ { "a" : [ 0, 1, 2 ] }, { "b" : [ 3, 4, 5 ] } ]

最终代码to-json()在末尾添加了一个调用。要弄清楚中间步骤中发生了什么,最简单的方法可能是向您显示每个单独数据结构的输出。打印@json数组会.say for @json;返回以下内容:

{key => a, value => 0}
{key => a, value => 1}
{key => a, value => 2}
{key => b, value => 3}
{key => b, value => 4}
{key => b, value => 5}

%accum仅使用 .<key> => .<value>(换句话说,不使用)生成哈希.map(*.Num).Slip通过调用返回以下内容.say for %accum.sort;

[ { "a" : [ [ "0" ], [ "1" ], [ "2" ] ] }, { "b" : [ [ "3" ], [ "4" ], [ "5" ] ] } ]

Num请注意,您可以使用、等函数调用对值进行各种操作Int,并且可以通过调用Slip如顶部发布的代码来“展平”。

Raku 的JSON::Tiny模块默认会对值进行字符串化,如果您希望这些字符串化值作为单个列表(而不是子列表),请将Slip它们放在一起。

下面,字符串化的值Slip组合在一起:

~$ raku -MJSON::Tiny -e 'my @json = from-json($_).list given slurp;  \
         my %accum.push: .<key> => .<value>.Slip for @json>>.split(", ");  \  
        .say for to-json(%accum.sort);'  file
[ { "a" : [ "0", "1", "2" ] }, { "b" : [ "3", "4", "5" ] } ]

因此,这里有很多用于数据输出的选项,最简单的是采用顶部的代码并删除最终的to-json()调用,以查看 Raku 的%accum哈希的“对”表示:

a => [0 1 2]
b => [3 4 5]

https://raku.land/cpan:MORITZ/JSON::Tiny
https://raku.org

相关内容