[英]Converting JSON with arrays to CSV using jq
我发现自己处在JSON领域,并尝试使用jq
将其转换为JSON。 我正在尝试将以下结构转换为CSV:
{
"Action": "A1",
"Group": [
{
"Id": "10",
"Units": [
"1"
]
}
]
}
{
"Action": "A2",
"Group": [
{
"Id": "11",
"Units": [
"2"
]
},
{
"Id": "20",
"Units": []
}
]
}
{
"Action": "A1",
"Group": [
{
"Id": "26",
"Units": [
"1",
"3"
]
}
]
}
{
"Action": "A3",
"Group": null
}
编号介于10-99和1-5单元之间。 预期的输出将是(带引号或不带引号,逗号分隔与否,为清楚起见,我使用了管道分隔符):
Action|Group|Unit1|Unit2|Unit3|Unit4|Unit5
A1|10|1|0|0|0|0
A2|11|0|1|0|0|0
A2|20|0|0|0|0|0
A1|26|1|0|1|0|0
A3|0|0|0|0|0|0
我已经玩了一段时间( history | grep jq | wc -l
表示107),但是在将键彼此组合在一起方面并没有取得任何实质性进展,我基本上只是在获取键列表( jq
n00b )。
更新:
测试解决方案(对不起,有点慢),我注意到数据中还包含带有"Group": null
记录"Group": null
s,即:
{
"Action": "A3",
"Group": null
}
(在主测试数据集上添加了几行),从而导致错误: jq: error (at file.json:61): Cannot iterate over null (null)
。 预期输出为:
A3|0|0|0|0
有没有一种简单的方法?
如果不知道单位列的集合,这是一个通用的解决方案:
def normalize: [ # convert input to array of flattened objects e.g.
inputs # [{"Action":"A1","Group":"10","Unit1":"1"}, ...]
| .Action as $a
| .Group[]
| {Action:$a, Group:.Id}
+ reduce .Units[] as $u ({};.["Unit\($u)"]="1")
];
def columns: # compute column names
[ .[] | keys[] ] | unique ;
def rows($names): # generate row arrays
.[] | [ .[$names[]] ] | map( .//"0" );
normalize | columns as $names | $names, rows($names) | join("|")
示例运行(假设filter.jq
中的filter.jq
和data.json
data)
$ jq -Mnr -f filter.jq data.json
Action|Group|Unit1|Unit2|Unit3
A1|10|1|0|0
A2|11|0|1|0
A2|20|0|0|0
A1|26|1|0|1
在这个特定的问题中,由unique
完成的排序与我们想要的列输出匹配。 如果不是这种情况, columns
将更加复杂。
许多复杂性来自于不了解最终的Unit列集。 如果单位固定且相当小(例如1-5),则可以使用更简单的滤波器:
["\(1+range(5))"] as $units
| ["Action", "Group", "Unit\($units[])"]
, ( inputs
| .Action as $a
| .Group[]
| [$a, .Id, (.Units[$units[]|[.]] | if .!=[] then "1" else "0" end) ]
) | join("|")
样品运行
$ jq -Mnr '["\(1+range(5))"] as $units | ["Action", "Group", "Unit\($units[])"], (inputs | .Action as $a | .Group[] | [$a, .Id, (.Units[$units[]|[.]] | if .!=[] then "1" else "0" end) ] ) | join("|")' data.json
Action|Group|Unit1|Unit2|Unit3|Unit4|Unit5
A1|10|1|0|0|0|0
A2|11|0|1|0|0|0
A2|20|0|0|0|0|0
A1|26|1|0|1|0|0
为了处理这种情况时Group
可能是null
的最简单的方法是使用的变化峰值的建议。 例如
["\(1+range(5))"] as $units
| ["Action", "Group", "Unit\($units[])"]
, ( inputs
| .Action as $a
| ( .Group // [{Id:"0", Units:[]}] )[] # <-- supply default group if null
| [$a, .Id, (.Units[$units[]|[.]] | if .!=[] then "1" else "0" end) ]
) | join("|")
这种情况适用于“单位”(n)列数事先已知的情况。 它只是@ jq170717的实现的变体。
如果n
的给定值太小,则使用max
可以确保行为合理。 在这种情况下,输出中的列数将有所不同。
以下已通过jq 1.5版和master版进行了测试; 请参阅以下有关jq早期版本的必要调整。
调用:jq -nr -f tocsv.jq data.json
tocsv.jq:
# n is the number of desired "Unit" columns
def tocsv(n):
def h: ["Action", "Group", "Unit\(range(1;n+1))"];
def i(n): reduce .[] as $i ([range(0;n)|"0"]; .[$i]="1");
def p:
inputs
| .Action as $a
| .Group[]
| [$a, .Id] + (.Units | map(tonumber-1) | i(n));
h,p | join(",") ;
tocsv(5)
上面编写的方式是,如果您想获得所有好处,只需用对@csv
或@tsv
的调用来替换要join
的调用。 但是,在那种情况下,您可能希望在指标函数i
使用0
和1
而不是"0"
和"1"
。
$ jq -nr -f tocsv.jq data.json
Action,Group,Unit1,Unit2,Unit3
A1,10,1,0,0
A2,11,0,1,0
A2,20,0,0,0
A1,26,1,0,1
对于jq 1.3或1.4,将inputs
更改为.[]
,并使用以下命令:
jq -r -s -f tocsv.jq data.json
处理"Group":null
情况的最简单方法可能是在| .Group[]
之前添加以下行| .Group[]
| .Group[]
:
| .Group |= (. // [{Id:"0", Units:[]}])
这样,您还可以轻松更改“ Id”的“默认”值。
这是针对“单位”(n)列数事先未知的情况。 它避免了一次读取整个文件,而是分三个主要步骤进行:通过“提要”以紧凑的形式收集相关信息; n被计算; 并形成完整的行。
为简单起见,以下内容适用于1.5版或更高版本的jq,并使用@csv
。 如果使用jq 1.4,则可能需要进行细微调整,具体取决于有关输出的详细要求。
jq -nr -f tocsv.jq input.json
# Input: a stream of JSON objects.
# Output: a stream of arrays.
def synopsis:
inputs
| .Action as $a
| .Group[]
| [$a, .Id, (.Units|map(tonumber-1))];
# Input: an array of arrays
# Output: a stream of arrays suitable for @csv
def stream:
def h(n): ["Action", "Group", "Unit\(range(1;n+1))"];
def i(n): reduce .[] as $i ([range(0;n)|0]; .[$i]=1);
(map(.[2] | max) | max + 1) as $n
| h($n),
(.[] | .[0:2] + (.[2] | i($n)))
;
[synopsis] | stream | @csv
"Action","Group","Unit1","Unit2","Unit3"
"A1","10",1,0,0
"A2","11",0,1,0
"A2","20",0,0,0
"A1","26",1,0,1
处理"Group":null
情况的最简单方法可能是在| .Group[]
之前添加以下行| .Group[]
| .Group[]
:
| .Group |= (. // [{Id:"0", Units:[]}])
这样,您还可以轻松更改“ Id”的“默认”值。
对于“单位”(n)列数事先未知的情况,这是一种对内存要求最低的解决方案。 在第一遍中,计算n。
这是第二遍:
# Output: a stream of arrays.
def synopsis:
inputs
| .Action as $a
| .Group |= (. // [{Id:0, Units:[]}])
| .Group[]
| [$a, .Id, (.Units|map(tonumber-1))];
def h(n): ["Action", "Group", "Unit\(range(1;n+1))"];
# Output: an array suitable for @csv
def stream(n):
def i: reduce .[] as $i ([range(0;n)|0]; .[$i]=1);
.[0:2] + (.[2] | i) ;
h($width), (synopsis | stream($width)) | @csv
jq -rn --argjson width $(jq -n '
[inputs|(.Group//[{Units:[]}])[]|.Units|map(tonumber)|max]|max
' data.json) -f stream.jq data.json
这是附加了“ null”记录({“ Action”:“ A3”,“ Group”:null})的输出:
"Action","Group","Unit1","Unit2","Unit3"
"A1","10",1,0,0
"A2","11",0,1,0
"A2","20",0,0,0
"A1","26",1,0,1
"A3",0,0,0,0
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.