Как объединить большой, сложный, глубоко вложенный файл JSON в несколько файлов CSV с помощью идентификатора ссылки - PullRequest
2 голосов
/ 04 июля 2019

У меня есть сложный файл JSON (~ 8 ГБ), содержащий общедоступные данные для предприятий.Мы решили разбить файлы на несколько файлов CSV (или вкладок в формате .xlsx), чтобы клиенты могли легко использовать данные.Эти файлы будут связаны столбцом / ключом NZBN.

Я использую R и jsonlite для чтения небольшого образца (до масштабирования до полного файла).Я предполагаю, что мне нужен какой-то способ указать, какие ключи / столбцы идут в каждом файле (т. Е. У первого файла будут заголовки: australianBusinessNumber, australianCompanyNumber, australianServiceAddress, у второго файла будут заголовки: AnnualReturnFilingMonth, yearReturnLastFiled, countryOfOrigin ...)

Вот пример двух предприятий / организаций (я также скопировал некоторые данные, поэтому игнорирую фактические значения): тестовый файл

Я прочиталпочти каждый пост на с / о подобных вопросов, и ни один, кажется, не дает мне никакой удачи.Я пробовал варианты purrr, * применения команд, пользовательских функций выравнивания и jqr (r-версия jq - выглядит многообещающе, но я не могу ее запустить).

Вот попытка создания моих отдельных файлов, но я не уверен, как включить идентификатор ссылки (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)

Я не уверен, проще ли разделить файлы во время процесса сглаживания / споров, или просто полностью сгладить весь файл, чтобы у меня была только одна строка для каждой организации / организации (а затем собратьстолбцы по мере необходимости) - меня беспокоит только то, что мне нужно увеличить его до ~ 1,3 миллиона узлов (файл JSON 8 ГБ).

В идеале я хотел бы, чтобы файлы CSV разделялись каждый раз, когда появляется новая коллекция, изначения в коллекции станут столбцами для новой csv / tab.

Любая помощь или советы будут высоко оценены.

------- ОБНОВЛЕНИЕ ------

Обновленный, поскольку мой вопрос был немного расплывчатым, я думаю, что все, что мне нужно, - это какой-то код для создания одной из таблиц / таблиц csv, и я выполняю репликацию для других коллекций.

Скажем, например, яхотел крсоздайте CSV-файл из следующих элементов:

  • entityName (уникальный идентификатор ссылки)
  • nzbn (уникальный идентификатор ссылки)
  • emailAddress__uniqueIdentifier
  • emailAddress__emailAddress
  • emailAddress__emailPurpose
  • emailAddress__emailPurposeDescription
  • emailAddress__startDate

Как мне поступить об этом?

Ответы [ 2 ]

0 голосов
/ 06 июля 2019

Если входной 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).

0 голосов
/ 04 июля 2019

Я не уверен, сколько существует уровней вложенности

Это даст ответ на этот вопрос довольно эффективно:

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 . Это создаст структурную схему.

"Скажем, например, я хотел создать CSV из следующих элементов:"

Вот решение jq, кроме заголовков:

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

1022 * акционерного капитала * Данные о долевом владении сложны, поэтому ниже я использовал функцию to_table, определенную в другом месте на этой странице. Пример данных не включает в себя поле «название компании», поэтому ниже я добавил поле «индекс компании» на основе 0: .companies.entity[] | [.entityName, .nzbn] as $ix | .company | range(0;length) as $cix | .[$cix] | $ix + [$cix] + (.shareholding[] | to_table(false)) JQR

В приведенных выше решениях используется автономный исполняемый файл jq, но все идет хорошо, тривиально использовать те же фильтры с jqr , хотя для использования include jq может быть проще всего указать путь явно, например:

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