【问题标题】:Convert text file with line breaks to json with jq使用 jq 将带有换行符的文本文件转换为 json
【发布时间】:2020-02-27 01:26:20
【问题描述】:

我已经看到很多使用 jq 将文本文件转换为 json 的示例,但我遇到了一些可能很明显的问题。我的输入文件格式如下:

key1: string1
key2: string1

key1: string3
key2: string3

我怎样才能把它翻译成:

[
  {"key1":"string1", "key2": "string2"},
  {"key1":"string3", "key2": "string4"}
]

我尝试将 inputs 与 jq 一起使用,类似于 jq -R -n -c '[inputs|split(":")|{(.[0]):.[1]}] | add',但一旦文件中有换行符,它就会失败:

jq: error (at result.txt:8): Cannot use null (null) as object key.

谢谢

【问题讨论】:

    标签: json shell jq


    【解决方案1】:

    由于减少被广泛理解,这里有一个简单的基于reduce 的解决方案,它不需要在key:value 行组之间换行。也就是说,在构造数组时,一旦遇到前一个对象中出现的键,就会启动一个新对象。

    < input.txt jq -nR '
      reduce (inputs
              | select(length > 0) 
              | capture("(?<k>^[^:]*): *(?<v>.*)") 
              | {(.k):.v}) as $in (null;
        if . == null then [$in] 
        elif (.[-1] | has($in|keys_unsorted[0])) then . + [$in] 
        else .[-1] += $in end)'
    

    【讨论】:

      【解决方案2】:

      这是一个简洁但相当健壮的解决方案,它假设使用 -n 和 -R 选项调用 jq(例如,jq -nR ...

      [foreach (inputs, null) as $in (null;
        if ($in|length) == 0
        then .out = .object | .object = null
        else .object += ($in | capture("(?<k>^[^:]*): *(?<v>.*)") | {(.k):.v})
        | .out = null
        end;
        .out // empty) ]
      

      这里,一个关键点(不是双关语)是使用capture 而不是splitsplits,以防值包含冒号。还要注意成语foreach (inputs,null) as ... 的使用。这可确保正确处理最后一个 key:value 输入。

      【讨论】:

      • 嘿。我不知道null | .foo = "bar" 会构造{"foo": "bar"} 而不仅仅是一个错误。这很方便。
      • jq 设计得非常方便:-0
      【解决方案3】:

      这是一个非常好的减速器用例。

      在其标准输入上运行您的文档,脚本:

      #!/usr/bin/env jq -Rncf
      # ...caveat: not all platforms support more than two words on the shebang
      #            you may need to stop using env and pass a full path to your jq binary!
      
      def input_line_to_object:
        "^(?<key>[^:]+): ?(?<value>.*)$" as $re |
        (capture($re) // ("line \(.) does not parse" | halt_error(1))) | {(.key):.value};
      
      reduce (inputs, "") as $item (
        # Initializer. .[0] == current object; .[1] == list of objects already constructed
        [{}, []];
        # Handler. Run for each line, and the final "" at the end.
        if $item == "" then
          if .[0] == {} then
            . # no new object is actually ready; maybe we had two newlines in a row?
          else
            # between objects: append .[0] to .[1], and clear .[0]
            [{}, .[1]+[.[0]]]
          end
        else
          # new data in an existing object; add new datum to .[0], leave .[1] alone
          [.[0] + ($item | input_line_to_object), .[1]]
        end
      ) | .[1] # take the completed list in the .[1] position
      

      ...作为输出发射:

      [{"key1":"string1","key2":"string1"},{"key1":"string3","key2":"string3"}]
      

      【讨论】:

      • @peak,欢迎您提出如何让这更优雅的想法。
      • 感谢邀请!发布单独的答案似乎更容易,尽管唯一的实质性区别是我避免了splitsplits。我想你会同意使用对象作为累加器而不是数组更容易消化?
      • 嗯。我需要仔细看一下(也许稍后我会清醒一点!)以了解您正在使用的基于foreach 的实现的细节。确实,使用capture() 肯定更好;当值包含: 时,我没有考虑过这种情况。
      • 顺便说一句,由于您鼓励我再看一下解析的稳健性,我最终利用它为行与正则表达式不匹配的情况添加显式错误处理的机会(缺少后跟冒号的键)。
      • 有趣的是,我的foreachreduce 解决方案都有效地忽略了这些行。也许在 SO 上下文中,沉默的行为可以通过沉默的要求来证明?
      猜你喜欢
      • 2021-12-05
      • 1970-01-01
      • 1970-01-01
      • 2018-06-21
      • 2018-08-20
      • 2019-01-26
      • 1970-01-01
      • 2021-11-14
      相关资源
      最近更新 更多