如何使用JQ将任意嵌套的JSON转换为CSV-这样您就可以将其转换回来? [英] How to convert arbitrary nested JSON to CSV with jq – so you can convert it back?
本文介绍了如何使用JQ将任意嵌套的JSON转换为CSV-这样您就可以将其转换回来?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!
问题描述
如何使用jq将任意JSON对象数组转换为CSV,而此数组中的对象是嵌套的?
StackOverflow有大量的问题/答案,其中引用了特定输入或输出字段,但我希望有一个通用的解决方案,
- 包括标题行
- 适用于任何JSON输入,包括嵌套数组+对象
- 允许具有其他记录中存在的键的缺失值的记录
- 不会硬编码任何字段名称,
- 允许在需要时将CSV转换回嵌套的JSON结构,并且
- 使用密钥路径作为标头名称(请参阅以下说明)。
点符号
很多使用json的产品(如CouchDB、MongoDB、…)和库(如Lodash、…)使用允许访问嵌套属性值/子字段的语法变体,方法是将关键字片段与字符(通常为点符号)连接。这样的键路径的一个示例是"a.b.0.c"
引用此JSON代码段中的深度嵌套属性:
{
"a": {
"b": [
{
"c": 123,
}
]
}
}
注意事项:在大多数情况下,使用此方法是一种实用的解决方案,但这意味着要么必须禁止属性名称中的点字符,要么必须发明一个更复杂(且绝对从未使用过的属性名称)来转义属性名称中的点/访问嵌套字段。MongoDB简单地banned usage of "."
in documents until v5.0,一些库有用于字段访问的变通方法(Lodash example)。
尽管如此,为简单起见,解决方案应该在CSV输出的头中为嵌套属性使用所描述的点语法。如果存在解决此问题的解决方案变体,例如使用JSONPath。
作为输入的JSON数组示例
[
{
"a": {
"b": [
{
"c": 123
}
]
}
},
{
"a": {
"b": [
{
"c": "foo " bar",
"d": "qux"
}
]
}
},
{
"a": {
"b": [
{
"d": 456
}
]
}
}
]
CSV输出示例
输出应具有包含所有字段的标头(即使第一个数组中的对象没有为所有现有键路径定义值)。
若要使输出可由人类直观地编辑,每行应表示输入数组中的一个对象。
预期输出应如下所示:
"a.b.0.c","a.b.0.d"
123,
"foo "" bar","qux"
,456
命令行
这就是我需要的:
cat example.json | jq <MISSING CODE HERE>
推荐答案
以下<[2-1]和fromcsv
函数提供了对所述问题的解决方案,除了关于头的要求(6)的一个复杂性。本质上,通过添加矩阵转置步骤,可以使用此处给出的函数来满足此要求。
在该示例中,给出了一个对象数组,但实际上任何有效的JSON文档流都可以用作tocsv
的输入;由于JQ的魔力,fromcsv
将重新创建原始流(从实体到实体相等的意义上)。
tocsv
函数可能并非所有CSV处理器都能理解。在……里面
具体来说,请注意这里定义的tocsv
函数映射
在JSON字符串中嵌入换行符或两个字符的键名称
字符串&Quot;
";(即直译反斜杠后跟字母";n";);
逆运算执行逆转换以满足
往返要求(&Q;)。
(使用tail
只是为了简化表示;它将
修改解决方案以使其成为唯一的JQ解决方案非常简单。)
CSV是在假设任何值都可以是
包括在字段中,只要(A)该字段被引用,以及(B)
字段中的双引号是双引号。任何支持往返的通用解决方案都必然是
有点复杂。这里提出的解决方案的主要原因是
比人们想象的更复杂是因为第三列是
添加,部分原因是为了更容易区分整数和
整数值字符串,但主要是因为这样做很容易
区分JQ生成的大小为1和大小为2的数组
--stream
选项。不用说,还有其他方法
这些问题是可以解决的;给JQ的电话数量可以
也会减少。
该解决方案以一个测试脚本的形式呈现,该脚本检查一个有说服力的测试用例的往返需求:
#!/bin/bash
function json {
cat<<EOF
[
{
"a": 1,
"b": [
1,
2,
"1"
],
"c": "d",ef",
"embed"ed": "quote",
"null": null,
"string": "null",
"control characters": "au0000c",
"newline": "a
b"
},
{
"x": 1
}
]
EOF
}
function tocsv {
jq -ncr --stream '
(["path", "value", "stringp"],
(inputs | . + [.[1]|type=="string"]))
| map( tostring|gsub(""";"""") | gsub("
"; "\n"))
| ""(.[0])","(.[1])",(.[2])"
'
}
function fromcsv {
tail -n +2 | # first duplicate backslashes and deduplicate double-quotes
jq -rR '"[(gsub("\\";"\\") | gsub("""";"\"") ) ]"' |
jq -c '.[2] as $s
| .[0] |= fromjson
| .[1] |= if $s then . else fromjson end
| if $s == null then [.[0]] else .[:-1] end
# handle newlines
| map(if type == "string" then gsub("\\n";"
") else . end)' |
jq -n 'fromstream(inputs)'
}
# Check the roundtrip:
json | tocsv | fromcsv | jq -s '.[0] == .[1]' - <(json)
以下是将由json | tocsv
生成的csv,只是so似乎不允许文字为空,所以我将其替换为