使用jq,如何将任意JSON编码的浅层对象数组转换为CSV?
本网站上有大量的问答,涉及硬编码字段的特定数据模型,但是对于任何JSON,这个问题的答案都应该适用,唯一的限制是它是一个具有标量属性的对象数组(没有深层/复杂/子对象,因为扁平化这些是另一个问题)。结果应该包含一个给出字段名称的标题行。优先选择保留第一个对象字段顺序的答案,但这不是必需的。结果可以用双引号括起所有单元格,或者只括起需要引号的单元格(例如'a,b')。
示例
1.输入:
[
{"code": "NSW", "name": "New South Wales", "level":"state", "country": "AU"},
{"code": "AB", "name": "Alberta", "level":"province", "country": "CA"},
{"code": "ABD", "name": "Aberdeenshire", "level":"council area", "country": "GB"},
{"code": "AK", "name": "Alaska", "level":"state", "country": "US"}
]
可能的输出:
code,name,level,country
NSW,New South Wales,state,AU
AB,Alberta,province,CA
ABD,Aberdeenshire,council area,GB
AK,Alaska,state,US
可能的输出:
"code","name","level","country"
"NSW","New South Wales","state","AU"
"AB","Alberta","province","CA"
"ABD","Aberdeenshire","council area","GB"
"AK","Alaska","state","US"
1.输入:
[
{"name": "bang", "value": "!", "level": 0},
{"name": "letters", "value": "a,b,c", "level": 0},
{"name": "letters", "value": "x,y,z", "level": 1},
{"name": "bang", "value": "\"!\"", "level": 1}
]
型
可能的输出:
name,value,level
bang,!,0
letters,"a,b,c",0
letters,"x,y,z",1
bang,"""!""",0
可能的输出:
"name","value","level"
"bang","!","0"
"letters","a,b,c","0"
"letters","x,y,z","1"
"bang","""!""","1"
7条答案
按热度按时间flvtvl501#
首先,获取一个包含对象数组输入中所有不同对象属性名称的数组,这些名称将成为CSV的列:
然后,对于对象数组输入中的每个对象,将获得的列名Map到对象中相应的属性,这些属性将成为CSV的行。
最后,将列名放在行之前,作为CSV的标题,并将生成的行流传递给
@csv
过滤器。现在一起来,记住使用
-r
标志来获取原始字符串的结果:e4yzc0pl2#
瘦子
或:
细节
一边
描述细节是很棘手的,因为jq是面向流的,这意味着它对JSON数据序列而不是单个值进行操作。输入JSON流被转换为某种内部类型,该类型通过过滤器传递,然后在程序结束时编码为输出流。内部类型不是由JSON建模的。并且不作为命名类型存在。通过检查空索引的输出可以很容易地证明这一点(
.[]
)或逗号运算符(可以用调试器直接检查它,但那是根据jq的内部数据类型,而不是JSON背后的概念数据类型)。注意,输出不是数组(
["a", "b"]
)。紧凑输出(-c
选项)显示每个数组元素(或,
过滤器的参数)成为输出中的一个单独对象(每个对象在单独的行上)。流类似于JSON-seq,但在编码时使用换行符而不是RS作为输出分隔符。因此,在本答案中,这种内部类型被称为通用术语“sequence”,“stream”被保留用于编码的输入和输出。
构造过滤器
第一对象的密钥可以用以下方式提取:
键通常会保持其原始顺序,但不能保证保持精确的顺序。因此,需要使用键来索引对象以获得相同顺序的值。如果某些对象具有不同的键顺序,这也将防止值出现在错误的列中。
为了将键作为第一行输出并使其可用于索引,它们被存储在一个变量中,管道的下一阶段引用该变量,并使用逗号操作符将头添加到输出流的前面。
逗号后面的表达式有点复杂,对象上的索引操作符可以取字符串序列(例如
"name", "value"
),返回这些字符串的属性值序列。$keys
是数组,不是序列,因此应用[]
将其转换为序列。然后可以将其传递到
.[]
这也会产生一个序列,因此使用数组构造函数将其转换为数组。
此表达式将应用于单个对象。
map()
用于将其应用于外部数组中的所有对象:最后,在这个阶段,它被转换为一个序列,这样每个项在输出中就变成了一个单独的行。
为什么把序列捆绑到
map
内的一个数组中,而在map
外却将其解捆绑呢?.[ $keys[] ]
生成一个序列,将map
应用于.[ $keys[] ]
的序列将生成一个值序列数组,但由于序列不是JSON类型,因此您得到的是一个包含所有值的扁平数组。每个对象的值需要保持独立,以便它们在最终输出中成为单独的行。
最后,序列通过
@csv
化器。候补
元素可以晚分隔,而不是早分隔。不使用逗号运算符来获取序列(将序列作为右操作数传递),而是将头序列(
$keys
) Package 在数组中,并使用+
附加值数组。在传递给@csv
之前,仍需要将其转换为序列。3pmvbmvn3#
下面的过滤器稍有不同,因为它将确保每个值都转换为字符串。(jq 1.5+)
过滤器:
filter.jq
nzk0hqpo4#
iecba09b5#
我创建了一个函数,它输出一个对象数组或数组到csv文件中,并带有标题。列将按照标题的顺序排列。
所以你可以这样使用它:
型
kninwzqo6#
圣地亚哥的程序的这个变体也是安全的,但是确保了第一个对象中的键名被用作第一列标题,其顺序与它们在该对象中出现的顺序相同:
myss37ts7#
一个简单的方法就是使用字符串连接。如果你的输入是一个合适的数组:
然后使用
.[]
:或者是一行一行的物体
只要这样做: