Как с помощью jq преобразовать произвольную JSON-кодировку массива мелких объектов в CSV?
На этом сайте есть множество вопросов и ответов, которые охватывают определенные модели данных, которые жестко кодируют поля, но ответы на этот вопрос должны работать с любым JSON, с единственным ограничением, что это массив объектов со скалярными свойствами (без глубоких / сложных / подобъекты, поскольку их выравнивание - другой вопрос). Результат должен содержать строку заголовка с именами полей. Предпочтение будет отдаваться ответам, которые сохраняют порядок полей первого объекта, но это не является обязательным требованием. Результаты могут заключать все ячейки в двойные кавычки или только те, которые требуют заключения в кавычки (например, «a, b»).
Примеры
Вход:
[ {"code": "NSW", "name": "New South Wales", "level":"state", "country": "AU"}, {"code": "AB", "name": "Alberta", "level":"province", "country": "CA"}, {"code": "ABD", "name": "Aberdeenshire", "level":"council area", "country": "GB"}, {"code": "AK", "name": "Alaska", "level":"state", "country": "US"} ]
Возможный выход:
code,name,level,country NSW,New South Wales,state,AU AB,Alberta,province,CA ABD,Aberdeenshire,council area,GB AK,Alaska,state,US
Возможный выход:
"code","name","level","country" "NSW","New South Wales","state","AU" "AB","Alberta","province","CA" "ABD","Aberdeenshire","council area","GB" "AK","Alaska","state","US"
Вход:
[ {"name": "bang", "value": "!", "level": 0}, {"name": "letters", "value": "a,b,c", "level": 0}, {"name": "letters", "value": "x,y,z", "level": 1}, {"name": "bang", "value": "\"!\"", "level": 1} ]
Возможный выход:
name,value,level bang,!,0 letters,"a,b,c",0 letters,"x,y,z",1 bang,"""!""",0
Возможный выход:
"name","value","level" "bang","!","0" "letters","a,b,c","0" "letters","x,y,z","1" "bang","""!""","1"
json2csv
Ответы:
Сначала получите массив, содержащий все различные имена свойств объекта во входных данных массива объектов. Это будут столбцы вашего CSV:
Затем для каждого объекта во входном массиве объектов сопоставьте полученные имена столбцов с соответствующими свойствами объекта. Это будут строки вашего CSV.
Наконец, поместите имена столбцов перед строками в качестве заголовка для CSV и передайте полученный поток строк
@csv
фильтру.Все вместе сейчас. Не забудьте использовать
-r
флаг, чтобы получить результат в виде необработанной строки:источник
$rows
присвоения переменной, просто вставив ее:(map(keys) | add | unique) as $cols | $cols, map(. as $row | $cols | map($row[.]))[] | @csv
$rows
это не обязательно должно быть присвоено переменной; Я просто подумал, что присвоение его переменной сделало объяснение лучше.Тощий
или:
Детали
В стороне
Описать детали сложно, потому что jq ориентирован на поток, то есть он работает с последовательностью данных JSON, а не с одним значением. Входной поток JSON преобразуется в некоторый внутренний тип, который проходит через фильтры, а затем кодируется в потоке вывода в конце программы. Внутренний тип не моделируется JSON и не существует как именованный тип. Это проще всего продемонстрировать, изучив вывод простого индекса (
.[]
) или оператора запятой (непосредственное изучение этого может быть выполнено с помощью отладчика, но это будет с точки зрения внутренних типов данных jq, а не концептуальных типов данных, стоящих за JSON) .Обратите внимание, что вывод не является массивом (что могло бы быть
["a", "b"]
). Компактный вывод (-c
опция) показывает, что каждый элемент массива (или аргумент,
фильтра) становится отдельным объектом в выводе (каждый находится в отдельной строке).Поток похож на JSON-seq , но использует символы новой строки, а не RS в качестве разделителя вывода при кодировании. Следовательно, этот внутренний тип упоминается в этом ответе общим термином «последовательность», причем «поток» зарезервирован для закодированных входных и выходных данных.
Построение фильтра
Ключи первого объекта можно извлечь с помощью:
Ключи обычно хранятся в исходном порядке, но сохранение точного порядка не гарантируется. Следовательно, их нужно будет использовать для индексации объектов, чтобы получить значения в том же порядке. Это также предотвратит попадание значений в неправильные столбцы, если некоторые объекты имеют другой порядок ключей.
Чтобы вывести ключи в качестве первой строки и сделать их доступными для индексации, они сохраняются в переменной. Затем следующий этап конвейера ссылается на эту переменную и использует оператор запятой для добавления заголовка к выходному потоку.
Выражение после запятой немного запутано. Оператор индекса объекта может принимать последовательность строк (например
"name", "value"
), возвращая последовательность значений свойств для этих строк.$keys
представляет собой массив, а не последовательность, поэтому[]
применяется для преобразования его в последовательность,который затем может быть передан
.[]
Это тоже создает последовательность, поэтому конструктор массива используется для преобразования ее в массив.
Это выражение должно применяться к одному объекту.
map()
используется для применения ко всем объектам внешнего массива:Наконец, на этом этапе он преобразуется в последовательность, поэтому каждый элемент становится отдельной строкой на выходе.
Зачем объединять последовательность в массив внутри
map
только для того, чтобы разделить ее снаружи?map
производит массив;.[ $keys[] ]
производит последовательность. Применениеmap
к последовательности from.[ $keys[] ]
приведет к созданию массива последовательностей значений, но поскольку последовательности не относятся к типу JSON, вместо этого вы получите плоский массив, содержащий все значения.Значения каждого объекта должны храниться отдельно, чтобы они становились отдельными строками в окончательном выводе.
Наконец, последовательность проходит через
@csv
форматтер.Альтернативный
Предметы можно разделить поздно, а не раньше. Вместо использования оператора запятой для получения последовательности (передача последовательности в качестве правого операнда) заголовок sequence (
$keys
) может быть заключен в массив и+
использоваться для добавления массива значений. Это все еще необходимо преобразовать в последовательность перед передачей в@csv
.источник
keys_unsorted
вместо этого,keys
чтобы сохранить порядок клавиш от первого объекта?[{"a":1,"b":2,"c":3}]
.Я создал функцию, которая выводит массив объектов или массивов в csv с заголовками. Столбцы будут в порядке заголовков.
Таким образом, вы можете использовать это так:
источник
Следующий фильтр немного отличается тем, что обеспечивает преобразование каждого значения в строку. (Примечание: используйте jq 1.5+)
Фильтр:
filter.jq
источник
unique
любом случае сортируется, поэтомуunique|sort
его можно упростить доunique
.-r
опции. В противном случае все кавычки"
станут экранированными, что не является допустимым CSV.Этот вариант программы Сантьяго также безопасен, но гарантирует, что имена ключей в первом объекте используются в качестве заголовков первых столбцов в том же порядке, в каком они появляются в этом объекте:
источник