【问题标题】:Convert TSV file to multiple JSON arrays with jq使用 jq 将 TSV 文件转换为多个 JSON 数组
【发布时间】:2021-05-13 21:55:42
【问题描述】:

我想使用open data IMDb,但他们以TSV格式提供,不太方便。

https://datasets.imdbws.com/title.crew.tsv.gz

tconst  directors   writers
tt0000238   nm0349785   \N
tt0000239   nm0349785   \N
tt0000240   \N  \N
tt0000241   nm0349785   \N
tt0000242   nm0617588   nm0617588
tt0000243   nm0349785   \N
tt0000244   nm0349785   \N
tt0000245   \N  \N
tt0000246   nm0617588   \N
tt0000247   nm0002504,nm0005690,nm2156608   nm0000636,nm0002504
tt0000248   nm0808310   \N
tt0000249   nm0808310   \N
tt0000250   nm0005717   \N
tt0000251   nm0177862   \N

我想将 TSV 数据转换为 JSON。

[
  {
    "tconst": "tt0000247",
    "directors": [
      "nm0005690",
      "nm0002504",
      "nm2156608"
    ],
    "writers": [
      "nm0000636",
      "nm0002504"
    ]
  },
  {
    "tconst": "tt0000248",
    "directors": [
      "nm0808310"
    ],
    "writers": [
      "\\N"
    ]
  }
]

我可以用命令做到这一点:

jq -rRs 'split("\n")[1:-1] |
         map([split("\t")[]|split(",")] | {
                 "tconst":.[0][0],
                 "directors":.[1],
                 "writers":.[2]
             }
    )' ./title.crew.tsv > ./title.crew.json

但是,文件变得非常大,我得到了内存不足的错误。

1.如何将这个 TSV 文件拆分成多个 JSON 文件,每个文件有 1000 条记录?

./title.crew.page1.json
./title.crew.page2.json
./title.crew.page3.json

2.如何排除空字段?要有一个空数组。

"writers": [ "\\N" ] -> "writers": [ ]

UPD(第二个问题已解决。):

jq -rRs 'split("\n")[1:-1] |
         map([split("\t")[]|split(",")] | 
         .[2] |= if .[0] == "\\N" then [] else . end | {
                 "tconst":.[0][0],
                 "directors":.[1],
                 "writers":.[2]
             }
    )' ./title.crew.tsv > ./title.crew.json
[
  {
    "tconst": "tt0000247",
    "directors": [
      "nm0005690",
      "nm0002504",
      "nm2156608"
    ],
    "writers": [
      "nm0000636",
      "nm0002504"
    ]
  },
  {
    "tconst": "tt0000248",
    "directors": [
      "nm0808310"
    ],
    "writers": []
  }
]

感谢您的回答。

【问题讨论】:

    标签: arrays json bash csv jq


    【解决方案1】:

    他们以 TSV 格式提供,这不是很方便。

    实际上,jq 和 TSV 配合得非常好,当然使用 jq 处理 TSV 文件不需要使用 -s(“slurp”)选项,这确实通常(但绝不总是)最好避免。

    如果您的目标只是生成“tconst”对象流,则可以逐行处理 TSV 文件;如果您想将该流组装成一个数组,那么您可以使用带有 -c 选项的 jq 来生成每行一个 JSON 对象的流,然后使用诸如 awk 之类的工具将它们组装在一起(即,只需添加左括号和右括号以及分隔逗号)。

    但是,在您的情况下,首先拆分 TSV 文件可能是最简单的(例如,使用 unix/linux/mac split 命令 -- 见下文),然后按照 jq 程序的行处理每个文件.由于您的块非常小(每个 1000 个对象),您甚至可以将 jq 与 -s 选项一起使用,但使用 inputs 和 -n 命令行选项同样容易:

    jq -n '[inputs]'
    

    或者您可以组合这些策略:拆分成块,并使用带有 -c 选项的 jq 处理每个块以生成一个流,并将每个这样的流组装成一个 JSON 数组。

    拆分

    关于将文件分割成块,例如:

    How to split a large text file into smaller files with equal number of lines?

    Split text file into smaller multiple text file using command line

    还有很多其他的。

    【讨论】:

      【解决方案2】:

      如果python是你的选择,那利用它怎么样,因为python的数据结构与json有很高的兼容性。请你试试:

      #!/usr/bin/python
      
      import json
      
      ary = []                                        # declare an empty array
      with open('./title.crew.tsv') as f:
          header = f.readline().rstrip().split('\t')  # read the header line and split
          for line in f:                              # iterate the following lines
              body = line.rstrip().split('\t')
              d = {}                                  # empty dictionary
              for i in range(0, len(header)):
                  if ',' in body[i]:                  # if the value contains ","
                      b = body[i].split(',')          # then split the value on it
                  else:
                      b = body[i]
                  if b == '\N':                       # if the value is "\N"
                      b = []                          # then replace with an empty array
                  d[header[i]] = b                    # generate an object
              ary.append(d)                           # append the object to the array
      print(json.dumps(ary, indent=2))
      

      输出:

      [
        {
          "directors": "nm0349785", 
          "tconst": "tt0000238", 
          "writers": []
        }, 
        {
          "directors": "nm0349785", 
          "tconst": "tt0000239", 
          "writers": []
        }, 
        {
          "directors": [], 
          "tconst": "tt0000240", 
          "writers": []
        }, 
      <..SNIPPED..>
      

      由于python 是一种通用编程语言,它在处理输入时具有很高的灵活性。将结果拆分为多个json文件也很容易。

      【讨论】:

        【解决方案3】:

        由于 1000 在当前上下文中是一个很小的数字,这里有一个不使用split 的解决方案;相反,它归结为一个两步管道。

        管道的第一部分包括使用 -c 选项调用 jq(用于将 TSV 转换为 JSON 数组流,每个块一个);如下所述。

        管道的第二部分将此数组流转换为所需的文件集,每个文件一个数组;这部分管道可以使用awk 或您选择的类似工具轻松实现,下面不再进一步讨论。

        program.jq

        # Assemble the items in the (possibly empty) stream into a 
        # (possibly empty) stream of arrays of length $n or less.
        # $n can be any integer greater than 0;
        # emit nothing if `stream` is empty.
        def assemble(stream; $n):
          # box the input to detect eos
          foreach ((stream|[.]), null) as $item ({};
             (.array|length) as $l
             | if $item == null # eos
               then .emit = (0 < $l and $l < $n)
               else if $l == $n
                    then .array = $item
                    else .array += $item
                    end
               | .emit = (.array|length == $n)
               end;
        
             if .emit then .array else empty end) ;
        
        
        def stream:
          inputs
          | split("\t")
          | map_values(if . == "\\N" then "" else . end)
          | map(split(","))
          | { tconst: .[0][0],
              directors: .[1],
              writers:   .[2] };
              
        assemble(stream; 1000)
        

        调用:

        要跳过标头,我们省略了 -n 命令行选项,如果没有标头则将使用该选项:

        jq -Rc -f program.jq input.tsv
        

        【讨论】:

          猜你喜欢
          • 2019-01-26
          • 1970-01-01
          • 1970-01-01
          • 2017-03-15
          • 2018-01-09
          • 2018-05-23
          • 1970-01-01
          • 1970-01-01
          • 2012-01-07
          相关资源
          最近更新 更多