繁体   English   中英

使用jq将具有数组的JSON转换为CSV

[英]Converting JSON with arrays to CSV using jq

我发现自己处在JSON领域,并尝试使用jq将其转换为JSON。 我正在尝试将以下结构转换为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
}

编号介于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),但是在将键彼此组合在一起方面并没有取得任何实质性进展,我基本上只是在获取键列表( jq n00b )。

更新:

测试解决方案(对不起,有点慢),我注意到数据中还包含带有"Group": null记录"Group": null s,即:

{
  "Action": "A3",
  "Group": null
}

(在主测试数据集上添加了几行),从而导致错误: jq: error (at file.json:61): Cannot iterate over null (null) 预期输出为:

A3|0|0|0|0

有没有一种简单的方法?

如果不知道单位列的集合,这是一个通用的解决方案:

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中的filter.jqdata.json data)

$ 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将更加复杂。

许多复杂性来自于不了解最终的Unit列集。 如果单位固定且相当小(例如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

在tio.runjqplay.org上 在线尝试


为了处理这种情况时Group可能是null的最简单的方法是使用的变化峰值的建议。 例如

  ["\(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("|")

在tio.runjqplay.org上 在线尝试

这种情况适用于“单位”(n)列数事先已知的情况。 它只是@ jq170717的实现的变体。

如果n的给定值太小,则使用max可以确保行为合理。 在这种情况下,输出中的列数将有所不同。

以下已通过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)

上面编写的方式是,如果您想获得所有好处,只需用对@csv@tsv的调用来替换要join的调用。 但是,在那种情况下,您可能希望在指标函数i使用01而不是"0""1"

验证

$ 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或jq 1.4

对于jq 1.3或1.4,将inputs更改为.[] ,并使用以下命令:

 jq -r -s -f tocsv.jq data.json

更新资料

处理"Group":null情况的最简单方法可能是在| .Group[]之前添加以下行| .Group[] | .Group[]

| .Group |= (. // [{Id:"0", Units:[]}])

这样,您还可以轻松更改“ Id”的“默认”值。

这是针对“单位”(n)列数事先未知的情况。 它避免了一次读取整个文件,而是分三个主要步骤进行:通过“提要”以紧凑的形式收集相关信息; n被计算; 并形成完整的行。

为简单起见,以下内容适用于1.5版或更高版本的jq,并使用@csv 如果使用jq 1.4,则可能需要进行细微调整,具体取决于有关输出的详细要求。

调用方式

jq -nr -f tocsv.jq input.json

tocsv.jq:

# 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[] | .Group[]

| .Group |= (. // [{Id:"0", Units:[]}])

这样,您还可以轻松更改“ Id”的“默认”值。

对于“单位”(n)列数事先未知的情况,这是一种对内存要求最低的解决方案。 在第一遍中,计算n。

流.jq

这是第二遍:

# 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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM