为了账号安全,请及时绑定邮箱和手机立即绑定

如何使用jq将任意简单的JSON转换为CSV?

如何使用jq将任意简单的JSON转换为CSV?

大话西游666 2019-11-25 10:51:42
使用jq,如何将浅层对象数组的任意JSON编码转换为CSV?这个站点上有很多问答,涵盖了对字段进行硬编码的特定数据模型,但是对于任何JSON,此问题的答案都应该有效,唯一的限制是它是具有标量属性的对象数组(无深度/复杂/子对象,如将它们展平是另一个问题)。结果应该包含一个标题行,给出字段名称。将优先选择保留第一个对象的字段顺序的答案,但这不是必须的。结果可以用双引号将所有单元格括起来,或者只将需要引用的单元格括起来(例如“ a,b”)。例子输入:[    {"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,countryNSW,New South Wales,state,AUAB,Alberta,province,CAABD,Aberdeenshire,council area,GBAK,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"输入:[    {"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,levelbang,!,0letters,"a,b,c",0letters,"x,y,z",1bang,"""!""",0可能的输出:"name","value","level""bang","!","0""letters","a,b,c","0""letters","x,y,z","1""bang","""!""","1"
查看完整描述

3 回答

?
回首忆惘然

TA贡献1847条经验 获得超11个赞

首先,获取一个包含对象数组输入中所有不同对象属性名称的数组。这些将是CSV的列:


(map(keys) | add | unique) as $cols

然后,对于对象数组输入中的每个对象,将获得的列名映射到对象中的相应属性。这些将是CSV的行。


map(. as $row | $cols | map($row[.])) as $rows

最后,将列名放在行之前,作为CSV的标题,然后将结果行流传递到@csv过滤器。


$cols, $rows[] | @csv

现在都在一起了。请记住使用该-r标志将结果作为原始字符串获取:


jq -r '(map(keys) | add | unique) as $cols | map(. as $row | $cols | map($row[.])) as $rows | $cols, $rows[] | @csv'


查看完整回答
反对 回复 2019-11-25
?
holdtom

TA贡献1805条经验 获得超10个赞

瘦的

jq -r '(.[0] | keys_unsorted) as $keys | $keys, map([.[ $keys[] ]])[] | @csv'

要么:


jq -r '(.[0] | keys_unsorted) as $keys | ([$keys] + map([.[ $keys[] ]])) [] | @csv'

细节

在旁边

描述细节是棘手的,因为jq是面向流的,这意味着它对JSON数据序列(而不是单个值)进行操作。输入的JSON流将转换为某种内部类型,并通过过滤器传递,然后在程序末尾的输出流中进行编码。内部类型不是由JSON建模的,并且不以命名类型存在。最简单的方法是检查裸索引(.[])或逗号运算符的输出(可以通过调试器直接检查它,但这是基于jq的内部数据类型,而不是JSON背后的概念数据类型) 。


$ jq -c'。[]'<<<'[“ a”,“ b”]'

“一种”

“ b”

$ jq -cn'“ a”,“ b”'

“一种”

“ b”

请注意,输出不是数组(应该是["a", "b"])。紧凑输出(该-c选项)表明,每个数组元素(或,过滤器的参数)在输出中均成为单独的对象(每个元素在单独的行上)。


流就像JSON-seq一样,但是在编码时使用换行符而不是RS作为输出分隔符。因此,此内部类型在此答案中由通用术语“序列”指代,其中“流”保留用于编码的输入和输出。


构造过滤器

可以使用以下命令提取第一个对象的键:


.[0] | keys_unsorted

密钥通常会保持其原始顺序,但不能保证保留确切的顺序。因此,将需要使用它们对对象建立索引以按相同顺序获取值。如果某些对象的键顺序不同,这也可以防止将值放在错误的列中。


为了将键作为第一行输出并使它们可用于索引,它们都存储在变量中。然后,管道的下一个阶段将引用此变量,并使用逗号运算符将标头添加到输出流之前。


(.[0] | keys_unsorted) as $keys | $keys, ...

逗号后的表达有点牵连。对象上的索引运算符可以采用字符串序列(例如"name", "value"),返回这些字符串的属性值序列。$keys是一个数组,而不是一个序列,因此[]可以将其转换为序列,


$keys[]

然后可以传递给 .[]


.[ $keys[] ]

这也产生一个序列,因此使用数组构造函数将其转换为数组。


[.[ $keys[] ]]

该表达式将应用于单个对象。map()用于将其应用于外部数组中的所有对象:


map([.[ $keys[] ]])

最后,在此阶段,它被转换为序列,因此每个项目在输出中成为单独的行。


map([.[ $keys[] ]])[]

为什么将序列捆绑到map唯一的数组中以在外部解绑呢?map产生一个数组;.[ $keys[] ]产生一个序列。将mapfrom 应用于序列.[ $keys[] ]会产生一个值序列数组,但是由于序列不是JSON类型,因此您将获得一个包含所有值的扁平数组。


["NSW","AU","state","New South Wales","AB","CA","province","Alberta","ABD","GB","council area","Aberdeenshire","AK","US","state","Alaska"]

每个对象的值需要保持分开,以便它们在最终输出中成为单独的行。


最后,序列通过@csv格式化程序传递。


备用

这些项目可以分离得较晚,而不是早期。代替使用逗号运算符获取序列(将序列作为正确的操作数传递),标头序列($keys)可以包装在数组中,并+用于附加值数组。在传递给之前,仍然需要将其转换为序列@csv。


查看完整回答
反对 回复 2019-11-25
?
慕的地6264312

TA贡献1817条经验 获得超6个赞

以下过滤器稍有不同,它将确保将每个值都转换为字符串。(注意:使用jq 1.5+)


# For an array of many objects

jq -f filter.jq (file)


# For many objects (not within array)

jq -s -f filter.jq (file)

过滤: filter.jq


def tocsv($x):

    $x

    |(map(keys)

        |add

        |unique

        |sort

    ) as $cols

    |map(. as $row

        |$cols

        |map($row[.]|tostring)

    ) as $rows

    |$cols,$rows[]

    | @csv;


tocsv(.)


查看完整回答
反对 回复 2019-11-25
  • 3 回答
  • 0 关注
  • 1079 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信