我发现自己身处JSON世界,我正试图使用jq
转换出来。我正在尝试将以下结构转换为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
}
其中Ids介于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)但是没有取得任何真正的进展将键与彼此组合,我基本上只是得到键列表({{ 1}} n00b)。
更新:
测试解决方案(抱歉,有点不对)我注意到数据也有jq
s的记录,即:
"Group": null
(以上几行添加到主测试数据集中)导致错误:{
"Action": "A3",
"Group": null
}
。预期的产出将是:
jq: error (at file.json:61): Cannot iterate over null (null)
有一个简单的方法吗?
答案 0 :(得分:2)
如果事先不知道单位列的集合,这是一般解决方案:
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
中的过滤器和data.json
中的数据)
$ 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
会更复杂。
很多复杂性来自处理不了解最终的单位列集。如果单位组固定且相当小(例如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
Try it online at tio.run或jqplay.org
要处理Group
可能null
的情况,最简单的方法是使用peak的变体建议。 E.g
["\(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("|")
答案 1 :(得分:2)
这适用于&#34; Unit&#34;的数量。列(n)是事先已知的。它只是@jq170717实现的变体。
如果max
的给定值太小,n
的使用是为了确保合理的行为。在这种情况下,输出中的列数会有所不同。
以下已经使用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)
上述内容的编写方式是,只要您想要获得所有好处,就可以通过拨打join
或@csv
来简单地将来电替换为@tsv
。但是,在这种情况下,您可能希望在指标函数0
中使用1
和"0"
而不是"1"
和i
。
$ 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 |= (. // [{Id:"0", Units:[]}])
这样你也可以轻松改变&#34;默认&#34;值&#34; Id&#34;。
答案 2 :(得分:2)
这是针对预先知道“单位”列数(n)未知的情况。它避免一次读取整个文件,并进行三个主要步骤:通过“概要”以紧凑的形式收集相关信息; n是计算的;并且形成了整行。
为简单起见,以下内容适用于jq 1.5或更高版本,并使用@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 |= (. // [{Id:"0", Units:[]}])
这样您也可以轻松更改“Id”的“默认”值。
答案 3 :(得分:1)
对于预先知道“单位”列数(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