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

时间:2019-07-04 15:01:40

标签: r json csv jq jsonlite

我有一个复杂的JSON文件(〜8GB),其中包含企业的公开可用数据。我们已决定将文件分成多个CSV文件(或.xlsx中的选项卡),以便客户端可以轻松使用数据。这些文件将通过NZBN列/键进行链接。

我正在使用R和jsonlite读取一个小样本(在放大到整个文件之前)。我猜想我需要某种方式来指定每个文件中使用的键/列(即,第一个文件将具有标头:australianBusinessNumber,australianCompanyNumber,australianServiceAddress,第二个文件将具有标头:AnnualReturnFilingMonth,annualReturnLastFiled,countryOfOrigin ...)

以下是两个企业/实体的示例(我也捆绑了一些数据,因此忽略了实际值):test file

我几乎阅读了所有关于类似问题的文章,似乎都没有给我带来任何好运。我尝试了purrr,* apply命令,自定义展平函数和jqr(“ jq”的r版本-看起来很有希望,但我似乎无法运行)的变体。

这是尝试创建单独的文件,但是我不确定如何包括链接标识符(NZBN)+我一直遇到其他嵌套列表(我不确定有多少层嵌套)

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)

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

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

任何帮助或技巧都将不胜感激。

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

由于我的问题有点模糊而更新,我认为我只需要一些代码即可生成其中一个csv / tab,然后为其他集合进行复制。

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

  • entityName(唯一链接标识符)
  • nzbn(唯一链接 标识符)
  • emailAddress__uniqueIdentifier
  • emailAddress__emailAddress
  • emailAddress__emailPurpose
  • emailAddress__emailPurposeDescription
  • emailAddress__startDate

我该怎么办?

2 个答案:

答案 0 :(得分:0)

  

我不确定有多少层嵌套

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

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

其中{。{3}}处有schema.jq。这将产生一个结构模式。

“例如,我想创建以下元素的csv:”

除了标题以外,这是一个jq解决方案:

.companies.entity[]
| [.entityName, .nzbn]
  + (.emailAddress[] | [.uniqueIdentifier, .emailAddress, .emailPurpose, .emailPurposeDescription, .startDate])
| @csv

股权

持股数据非常复杂,因此在下面,我使用了此页面其他地方定义的to_table函数。

示例数据不包含“公司名称”字段,因此在下面,我添加了一个基于0的“公司索引”字段:

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

jqr

以上解决方案使用独立的jq可执行文件,但一切顺利,将相同的过滤器与gist一起使用应该是微不足道的,尽管要使用jq的include,指定显式路径,例如:

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

答案 1 :(得分:0)

如果输入的JSON足够规则,则您 可能会发现下面的展平函数很有用,特别是因为它可以基于输入的叶元素的“路径”以字符串数组的形式发出标头,该标头可以任意嵌套:

# 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生成所需的表(不包含标题),可以编写:

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

(添加标题并检查一致性, 现在暂时作为练习,但在下面进行处理。)

生成多个文件

更有趣的是,您可以选择所需的级别,并自动生成多个表。有效地将输出分区到单独文件中的一种方法是使用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=$1".tsv"}'

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