【问题标题】:CSV to JSON using jq使用 jq 将 CSV 转换为 JSON
【发布时间】:2015-06-22 04:17:42
【问题描述】:

如果你有这样的 csv 数据集:

name, age, gender
john, 20, male
jane, 30, female
bob, 25, male

你能做到吗:

[ {"name": "john", "age": 20, "gender": "male"},
  {"name": "jane", "age": 30, "gender": "female"},
  {"name": "bob", "age": 25, "gender": "male"} ]

只使用 jq?

我发现this 文章显示了我正在尝试做的事情,但它使用标题字段到值的“手动”映射。我不需要/不想重命名标题字段并且有很多。我也不希望每次布局更改时都必须更改脚本/命令。

是否可以动态提取标题,然后将它们与 jq one-liner 的值结合起来?

【问题讨论】:

  • 您要求做的事情毫无意义。 jq 是一个以json为输入并生成输出的工具。 csv 不是 json。你不能指望这个工具来处理它,这不是它的用途。您需要使用可以处理 csv 的工具或其他脚本语言。
  • 而不是试图强制jq 这样做,它可以用一个几乎微不足道的 shell/sed 脚本来完成,例如基于stackoverflow.com/questions/4286469/…
  • 我是OP引用的文章的作者。虽然我提供的示例对于快速脏的 jq 项目很有用,但它不是很健壮。有一些很好的 CSV 工具可以使用,我会推荐其中的一种,例如:johnkerl.org/miller/doc(比如 CSV 的 jq)或者这个 NPM 包 npmjs.com/package/csv2json 或者这个 gem rubygems.org/gems/csv2json/versions/0.3.0

标签: json csv jq


【解决方案1】:
#! /bin/jq -fsR
[
    [
        split("\n")[]             |
        select(. != "")           |
        split(",")                
    ]                             | 
    .[0] as $header               |
    .[1:][]  as $data             |
    $header | reduce keys[] as $key ( {}; .[$header[$key]] = $data[$key] )
]

【讨论】:

  • 您的答案可以通过额外的支持信息得到改进。请edit 添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。你可以找到更多关于如何写好答案的信息in the help center
【解决方案2】:

这是 jq 的一个相当简单的“单线”版本,适用于“合理”大小的文件,对于非常大的文件,您需要一个不使用 slurp 的版本。我对 jq 还很陌生,我相信还有更好的方法可以做到这一点(也许只是增加一个索引值而不是存储在数据中)。如果你想让它更短更难阅读,你可以用 ./"\n" 和 ./"," 替换 "split"。注意:如果您确实需要逗号后的空格,可以在“,”上拆分或在逗号拆分后添加 |map(gsub("^\s+|\s+$";""))修剪前导和尾随空白。

jq -Rs 'split("\n")|map(split(",")|to_entries)|.[0] as $header|.[1:]|map(reduce .[] as $item ({};.[$header[$item.key].value]=$item.value))'

这是一个注释版本:

# jq -Rs
split("\n") | map( split(",") | to_entries ) # split lines, split comma & number
  | .[0] as $header # save [0]
  | .[1:] # and then drop it
  | map( reduce .[] as $item ( {}; .[$header[$item.key].value] = $item.value ) )

顶部非常简单:在换行符上拆分数据,然后将每个元素拆分为逗号,然后 to_entries 会将每个元素转换为键/值条目,并带有键编号(0.. N): {key:#, value:string}

然后它使用 map/reduce 获取每个元素并使用键/值对的对象替换它,使用编号键索引回标头以获取标签。对于那些新减少的人(像我一样),分号之前的第一个元素是初始化“累加器”(每次通过元素时修改的东西)所以 .[...] 正在修改累加器和 $item是我们正在操作的对象。

更新:我现在有一个更好的版本,它不使用 slurp,我们不使用 -n 选项,因为它会特别处理第一行:

jq -R 'split(",") as $h|reduce inputs as $in ([]; . += [$in|split(",")|. as $a|reduce range(0,length) as $i ({};.[$h[$i]]=$a[$i])])'

【讨论】:

    【解决方案3】:

    简而言之 - 是的,除了单线位。

    jq 通常非常适合文本处理,对于支持正则表达式的版本尤其如此。例如,使用正则表达式支持,给定问题陈述所需的修整是微不足道的。

    由于 jq 1.5rc1 包含正则表达式支持并且自 2015 年 1 月 1 日起可用,以下程序假定版本为 jq 1.5;如果您希望使其与 jq 1.4 一起使用,请参阅两个“For jq 1.4”cmets。

    还请注意,此程序无法处理 CSV 的所有通用性和复杂性。 (有关更普遍地处理 CSV 的类似方法,请参阅https://github.com/stedolan/jq/wiki/Cookbook#convert-a-csv-file-with-headers-to-json

    # objectify/1 takes an array of string values as inputs, converts
    # numeric values to numbers, and packages the results into an object
    # with keys specified by the "headers" array
    def objectify(headers):
      # For jq 1.4, replace the following line by: def tonumberq: .;
      def tonumberq: tonumber? // .;
      . as $in
      | reduce range(0; headers|length) as $i ({}; .[headers[$i]] = ($in[$i] | tonumberq) );
    
    def csv2table:
      # For jq 1.4, replace the following line by:  def trim: .;
      def trim: sub("^ +";"") |  sub(" +$";"");
      split("\n") | map( split(",") | map(trim) );
    
    def csv2json:
      csv2table
      | .[0] as $headers
      | reduce (.[1:][] | select(length > 0) ) as $row
          ( []; . + [ $row|objectify($headers) ]);
    
    csv2json
    

    示例(假设 csv.csv 是给定的 CSV 文本文件):

    $ jq -R -s -f csv2json.jq csv.csv
    [
      {
        "name": "john",
        "age": 20,
        "gender": "male"
      },
      {
        "name": "jane",
        "age": 30,
        "gender": "female"
      },
      {
        "name": "bob",
        "age": 25,
        "gender": "male"
      }
    ]
    

    【讨论】:

    • 我确认这确实有效(使用 jq 1.5 版测试)。模块化解决方案的精彩展示。无论如何,我需要一些时间来理解所有的结构。不错。
    • @peak 如何将其扩展为不将 "" 包装为 "\"\"" 并将 FALSE 转换为 false 并将 TRUE 转换为 true
    • @peak 我自己设法转换了布尔值,但字符串中的双引号仍然困扰着我。另外,我必须在哪里扩展 csv2json def 以仅转换“品牌”值为“MYBRAND”的行?
    • @philk - 也许您可以调整修剪以修剪最外面的双引号。我不太明白你的其余问题。也许它值得拥有自己的顶级 SO 问题?
    • 这个解决方案很好,但我发现它不能很好地处理 CSV 中包含逗号的引用值。也就是说,如果标题行包含name,age,而数据行包含"smith, bill",42,那么它将在JSON 中显示为{"name": "\"smith", "age": "bill\""}
    【解决方案4】:

    与米勒 (http://johnkerl.org/miller/doc/) 非常简单。使用这个 input.csv 文件

    name,age,gender
    john,20,male
    jane,30,female
    bob,25,male
    

    正在运行

    mlr --c2j --jlistwrap cat input.csv
    

    你会有

    [
    { "name": "john", "age": 20, "gender": "male" }
    ,{ "name": "jane", "age": 30, "gender": "female" }
    ,{ "name": "bob", "age": 25, "gender": "male" }
    ]
    

    【讨论】:

    • @user3041539 我不完全同意。确实,这是一个 jq 问题,但 jq 不是读取 csv 的工具,那么有必要给一个“x”问题,一个“y”回复。那我同意你的看法,但这是强制性的
    • 也许如果不清楚 - 我支持这个答案,并认为它应该排名更高。
    【解决方案5】:

    截至 2018 年,现代无代码解决方案是使用 Python 工具 csvkit 具有 csvjson data.csv > data.json

    查看他们的文档https://csvkit.readthedocs.io/en/1.0.2/

    如果您的脚本必须同时调试csvjson 格式,该工具包也非常方便和补充jq

    您可能还想查看一个名为visidata 的强大工具。这是一个与原始海报相似的screencast case study。也可以从visidata生成脚本

    【讨论】:

    • 这个问题是一个经典的 X/Y 问题 - X 的解决方案比完成 Y 所需的解决方案要容易得多 - xyproblem.info。感谢您回答 X!
    • 感谢csvkit 提示。很棒的工具!
    【解决方案6】:

    我玩了一个小游戏,然后想出了这个。但是这可能不是最好的方法,我很想看看你的尝试是什么样的,因为毕竟如果我们都找到了一个解决方案,我会肯定会好上一倍!

    但我会从以下内容开始:

    true as $doHeaders
    | . / "\n"
    | map(. / ", ")
    | (if $doHeaders then .[0] else [range(0; (.[0] | length)) | tostring] end) as $headers
    | .[if $doHeaders then 1 else 0 end:][]
    | . as $values
    | keys
    | map({($headers[.]): $values[.]})
    

    Working Example

    变量$doHeaders 控制是否读取顶行作为标题行。在您的情况下,您希望它是真实的,但我为未来的 SO 用户添加了它,因为,我今天吃了一顿丰盛的早餐,天气很好,为什么不呢?

    小解释:

    1) . / "\n" 按行分割...

    2) map(. / ", ") ... 和逗号 (Big gotcha: 在您的版本中,您需要使用基于正则表达式的拆分,因为这样您将在引号内的逗号上拆分也是。我只是使用它,因为它很简洁,这让我的解决方案看起来很酷,对吧?)

    3)if $doHeaders then...这里我们根据第一行的元素个数以及第一行是否是标题行来创建一个字符串键或数字的数组

    4) .[if $doHeaders then 1 else 0 end:] 好的,如果它是标题,则修剪顶行

    5) map({($headers[.]): $values[.]}) 上面我们遍历了前 csv 中的每一行,并将 $values 放入变量中,将键放入管道中。然后我们构造你想要的对象。

    当然,您会想要使用一些正则表达式来填充问题,但我希望这能让您开始。

    【讨论】:

    • 我只想回一下,但现在需要在评论中写更多内容。
    • 感谢您为此付出的努力!诚然,这更像是一个理论问题而不是实际问题。我最终在 bash 中做到了这一点,但一直在思考是否只能在 jq 中完成,所以我问了。以上很接近。它输出[ {"name": "john"}, {"age": 20}, {"gender": "male"}...
    • 如果我有需要的声誉,我会支持你:) 再次感谢。
    • 很高兴。哦- 哎呀,我在查询结束时错过了最后的| add,但我们走了。最好的。
    【解决方案7】:

    这是一个假设您使用 -s-R 选项运行 jq 的解决方案。

    [
      [                                               
        split("\n")[]                  # transform csv input into array
      | split(", ")                    # where first element has key names
      | select(length==3)              # and other elements have values
      ]                                
      | {h:.[0], v:.[1:][]}            # {h:[keys], v:[values]}
      | [.h, (.v|map(tonumber?//.))]   # [ [keys], [values] ]
      | [ transpose[]                  # [ [key,value], [key,value], ... ]
          | {key:.[0], value:.[1]}     # [ {"key":key, "value":value}, ... ]
        ]
      | from_entries                   # { key:value, key:value, ... }
    ]
    

    示例运行:

    jq -s -R -f filter.jq data.csv
    

    样本输出

    [
      {
        "name": "john",
        "age": 20,
        "gender": "male"
      },
      {
        "name": "jane",
        "age": 30,
        "gender": "female"
      },
      {
        "name": "bob",
        "age": 25,
        "gender": "male"
      }
    ]
    

    【讨论】:

    • jq 在解析过滤器文件jq: error: syntax error, unexpected ?//, expecting ';' or ')' (Unix shell quoting issues?) at <top-level>, line 8: | [.h, (.v|map(tonumber?//.))] # [ [keys], [values] ] jq: 1 compile error时出错
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-05-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多