如何将大型、复杂、深度嵌套的 JSON 文件扁平化为多个 CSV 文件链接标识符

How do I flatten a large, complex, deeply nested JSON file into multiple CSV files a linking identifier

我有一个复杂的 JSON 文件 (~8GB),其中包含企业的公开可用数据。我们已决定将文件拆分为多个 CSV 文件(或 .xlsx 中的选项卡),以便客户可以轻松使用数据。这些文件将由 NZBN column/key.

链接

我正在使用 R 和 jsonlite 读取一个小样本(在扩展到完整文件之前)。我猜我需要一些方法来指定每个文件中的 key/columns(即,第一个文件将具有 headers:australianBusinessNumber、australianCompanyNumber、australianServiceAddress,第二个文件将具有 headers : annualReturnFilingMonth, annualReturnLastFiled, countryOfOrigin...)

这是两个 businesses/entities 的示例(我也删除了一些数据,因此忽略了实际值):test file

我几乎读过 post 上关于 s/o 的所有类似问题,none 似乎给了我好运。我已经尝试过 purrr 的变体、*apply 命令、自定义扁平化函数和 jqr('jq' 的 r 版本 - 看起来很有希望,但我似乎无法 运行 它)。

这是创建我的单独文件的尝试,但我不确定如何包含链接标识符 (NZBN) + 我将 运行ning 保留到进一步的嵌套列表中(我不确定有多少层嵌套有)

bulk <- jsonlite::fromJSON("bd_test.json")

coreEntity <- data.frame(bulk$companies)
coreEntity <- coreEntity[,sapply(coreEntity, is.list)==FALSE] 

company <- bulk$companies$entity$company
company <- purrr::reduce(company, dplyr::bind_rows)

shareholding <- company$shareholding
shareholding <- purrr::reduce(shareholding, dplyr::bind_rows)

shareAllocation <- shareholding$shareAllocation
shareAllocation <- purrr::reduce(shareAllocation, dplyr::bind_rows)

我不确定在 flattening/wrangling 过程中拆分文件是否更容易,或者只是完全展平整个文件所以我每个 business/entity 只有一行(然后收集根据需要列)-我唯一担心的是我需要将其扩展到约 130 万个节点(8GB JSON 文件)。

理想情况下,我希望每次有新的 collection 时都拆分 csv 文件,并且 collection 中的值将成为新的 csv/tab 的列。

如有任何帮助或提示,我们将不胜感激。

-------- 更新 ------

已更新,因为我的问题有点含糊,我想我需要的只是一些代码来生成一个 csv's/tabs,然后我复制另一个 collections。

例如,我想创建一个包含以下元素的 csv:

我该怎么做?

i'm unsure how many levels of nesting there are

这将非常有效地提供答案:

jq '
  def max(s): reduce s as $s (null; 
    if . == null then $s elif $s > . then $s else . end);
   max(paths|length)' input.json

(有测试文件,答案是14。)

要获得数据的整体视图(架构),您可以 运行:

 jq 'include "schema"; schema' input.json

其中 schema.jq 可用于此 gist。这将产生一个结构模式。

"Say for example, I wanted to create a csv of the following elements:"

除了 headers:

之外,还有一个 jq 解决方案
.companies.entity[]
| [.entityName, .nzbn]
  + (.emailAddress[] | [.uniqueIdentifier, .emailAddress, .emailPurpose, .emailPurposeDescription, .startDate])
| @csv

持股

股权数据比较复杂,所以在下文中我使用了本页其他地方定义的to_table函数。

示例数据不包含 "company name" 字段,因此在下面,我添加了一个基于 0 的 "company index" 字段:

  .companies.entity[]
  | [.entityName, .nzbn] as $ix
  | .company
  | range(0;length) as $cix
  | .[$cix]
  | $ix + [$cix] + (.shareholding[] | to_table(false))

jqr

以上解决方案使用独立的 jq 可执行文件,但一切顺利,使用与 jqr 相同的过滤器应该是微不足道的,但使用 jq 的 include,可能最简单明确指定路径,例如:

include "schema" {search: "~/.jq"};

如果输入JSON足够规则,你 可能会发现以下展平函数很有用,特别是因为它可以基于输入的叶元素 "paths" 以字符串数组的形式发出 header,可以任意嵌套:

# to_table produces a flat array.
# If hdr == true, then ONLY emit a header line (in prettified form, i.e. as an array of strings);
# if hdr is an array, it should be the prettified form and is used to check consistency.
def to_table(hdr):
  def prettify: map( (map(tostring)|join(":") ));
  def composite: type == "object" or type == "array";

  def check:
     select(hdr|type == "array") 
     | if prettify == hdr then empty
       else error("expected head is \(hdr) but imputed header is \(.)")
       end ;

  . as $in
  | [paths(composite|not)]           # the paths in array-of-array form
  | if hdr==true then prettify
    else check, map(. as $p | $in | getpath($p))
    end;


例如,要为 .emailAddress 生成所需的 table(没有 headers),可以这样写:

.companies.entity[]
| [.entityName, .nzbn] as $ix
| $ix + (.emailAddress[] | to_table(false))
| @tsv

(添加 header 并检查一致性, 现在留作练习,但在下面处理。)

正在生成多个文件

更有趣的是,您可以 select 您想要的级别,并自动生成多个 table。将输出有效地划分为单独文件的一种方法是使用 awk。例如,您可以通过管道传输使用此 jq 过滤器获得的输出:

["entityName", "nzbn"] as $common
| .companies.entity[]
| [.entityName, .nzbn] as $ix
| (to_entries[] | select(.value | type == "array") | .key) as $key
| ($ix + [$key] | join("-")) as $filename
| (.[$key][0]|to_table(true)) as $header

# First emit the line giving all the headers:
| $filename, ($common + $header | @tsv),
# Then emit the rows of the table:
  (.[$key][]
   | ($filename,  ($ix + to_table(false) | @tsv)))

awk -F\t 'fn {print >> fn; fn=0;next} {fn=".tsv"}'

这将在每个文件中生成 header;如果要进行一致性检查,请将 to_table(false) 更改为 to_table($header)