jq:无法将对象转换为CSV格式,只能转换为数组。

13

我是 jq 的新手,我有一个来自 DynamoDB 表的 JSON 文件,我想将其转换为 CSV。这是我的 JSON 文件。

[
    {
        "SnsPublishTime": {
            "S": "2019-07-27T15:07:38.904Z"
        },
        "SESreportingMTA": {
            "S": "dsn; a8-19.smtp-out.amazonses.com"
        },
        "SESMessageType": {
            "S": "Bounce"
        },
        "SESDestinationAddress": {
            "S": "bounce@simulator.amazonses.com"
        },
        "SESMessageId": {
            "S": "0100016c33f91857-600a8e44-c419-4a02-bfd6-7f6908f5969e-000000"
        },
        "SESbounceSummary": {
            "S": "[{\"emailAddress\":\"bounce@simulator.amazonses.com\",\"action\":\"failed\",\"status\":\"5.1.1\",\"diagnosticCode\":\"smtp; 550 5.1.1 user unknown\"}]"
        }
    }
]

如果我运行这个程序,就可以获得正确的输出。

jq -r '.[] ' test.json

但如果我奔跑

jq -r '.[] |@csv' test.json

然后我遇到了一个错误:

jq:错误(位于test.json:22):对象({"SnsPublis ...)无法以CSV格式进行格式化,只能是数组

我该如何正确地将这个JSON转换为CSV?我试着谷歌了一个多小时,但似乎无法解决。

谢谢!


2
在CSV中表示嵌套结构并没有简单明了的方法。如果您能够指定您希望如何将其序列化为CSV(并且可能演示自己实现的可行尝试),也许我们可以帮助您实现。 - tripleee
1
谢谢@tripleee,您的关键字“nested”真帮助我缩小了搜索范围。这个命令已经足够满足我的需求。 code jq -r '.[] | [ .SnsPublishTime.S, .SESreportingMTA.S, .SESMessageType.S, .SESDestinationAddress.S]| @csv' test.json现在,由于某种原因,结果仍然包含引号,但我可以接受。 - Kliment
2个回答

17

以下是一个通用的JSON转CSV转换器,它有一个主要假设和一个次要假设。

主要假设是所有JSON实体都符合标准。在你的情况下,这意味着所有相应的对象具有相同的键(尽管顺序可能不同)。如果此假设被违反,将引发错误条件并停止处理。

次要假设是键名不包含点(“.”);如果任何键名包含点,则某些标题可能难以阅读或解析,因为标题是使用点作为join字符形成的。如果这是个问题,则可以尝试使用其他连接字符。

jq程序

def json2header:
  [paths(scalars)];

def json2array($header):
  json2header as $h
  | if $h == $header or (($h|sort) == ($header|sort))
    then [$header[] as $p | getpath($p)]
    else "headers do not match: expected followed by found paths:" | debug
    | ($header|map(join(".")) | debug)
    | ($h|map(join(".")) | debug)
    | "headers do not match" | error
    end ;

# given an array of conformal objects, produce "CSV" rows, with a header row:
def json2csv:
  (.[0] | json2header) as $h
  | ([$h[]|join(".")], (.[] | json2array($h))) 
  | @csv ;

# `main`
json2csv

调用

jq -rf json2csv.jq INPUT.json

输出结果

"SnsPublishTime.S","SESreportingMTA.S","SESMessageType.S","SESDestinationAddress.S","SESMessageId.S","SESbounceSummary.S"
"2019-07-27T15:07:38.904Z","dsn; a8-19.smtp-out.amazonses.com","Bounce","bounce@simulator.amazonses.com","0100016c33f91857-600a8e44-c419-4a02-bfd6-7f6908f5969e-000000","[{""emailAddress"":""bounce@simulator.amazonses.com"",""action"":""failed"",""status"":""5.1.1"",""diagnosticCode"":""smtp; 550 5.1.1 user unknown""}]"

变化:读取JSON流

有了上述基础设施,将符合规范的JSON实体流转换为带有标题的CSV格式也很容易。

def inputs2csv:
  json2header as $h
  | [$h[]|join(".")],
    json2array($h),
    (inputs|json2array($h))
  | @csv ;

# `main`
inputs2csv

图示说明对应对象中的键名不需要按相同顺序排列

[ {a:1, b: {c:3, d: [{e:4},{e:5, f:6}]}},
  {b: {d: [{e:4},{f:6, e:5}], c:3}, a:1}
 ] 
| json2csv

生成:

"a","b.c","b.d.0.e","b.d.1.e","b.d.1.f"
1,3,4,5,6
1,3,4,5,6

另一种变化

在某些情况下,可能不需要进行符合性检查,因此您会得到:

def json2array($header):
  [$header[] as $p | getpath($p)];

哇!谢谢 @peak,这真的帮了很多忙! - Kliment
嗨@peak,我创建了一个包含上述“jq程序”内容的jq文件,按照建议运行时出现“无法使用数字索引对象”的错误消息。有什么想法或者如何进行调试? - Chris
你可以尝试使用 debug 过滤器。 - peak

9

记录一下,这里有一个通用的JSON到CSV转换器,可以将任何JSON对象数组转换为带标题的CSV格式。这些对象没有限制,但转换不总是可逆的,输出单元格可能包含字符串化的复合实体--请参见“警告”。

json2csv

# emit a stream
def json2headers:
  def isscalar: type | . != "array" and . != "object";
  def isflat: all(.[]; isscalar);
  paths as $p
  | getpath($p)
  | if type == "array" and isflat then $p
     elif isscalar and (($p[-1]|type) == "string") then $p
     else empty end ;

def json2array($header):
  def value($p):
    try getpath($p) catch null
    | if type == "object" then null else . end;
  [$header[] as $p | value($p)];

def json2csv:
  ( [.[] | json2headers] | unique) as $h
  | ([$h[]|join("_") ],
     (.[]
      | json2array($h)
      | map( if type == "array" then map(tostring)|join("|") else tostring end)))
  | @csv ;

使用方法

使用json2csv.jq的一种方法是作为jq模块,例如:

jq -r -L. 'include "json2csv"; json2csv' input.json

如果输入是一个JSON对象的流:
jq -rn -L. 'include "json2csv"; [inputs]|json2csv' input.json

注意事项

  • 对于顶层数组中的每个对象,将计算到所有标量和标量值数组的路径集;如果该路径是对象值或对另一个对象无效,则该对象在输出中相应的单元格将为"null"

  • 扁平数组将转换为使用管道符分隔的值,因此如果输入包括像["1|2", ["3|4"]]这样的数组,它将无法与字符串值“1|2|3|4”等区分。如果这是一个问题,那么用作数组项分隔符的字符当然可以更改。

  • 标题之间也可能存在类似的冲突。

转换为TSV

sed 's/@csv/@tsv/' json2csv.jq > json2tsv.jq

1
对于像我这样从未导入过自定义jq模块的人来说,命令中的“-L .”部分指定了“json2csv.jq”文件的位置。如果您的脚本不在当前目录中,请将“.”更改为脚本的路径。我花了一段时间才弄清楚这一点。 - cmpickle

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接