Используя jq, как я могу разбить очень большой файл JSON на несколько файлов, каждый из которых содержит определенное количество объектов?
У меня есть большой файл JSON с 4 миллионами объектов. Каждый верхний уровень имеет несколько уровней, вложенных внутрь. Я хочу разделить это на несколько файлов по 10000 объектов верхнего уровня каждый (сохранив структуру внутри каждого). JQ должен быть в состоянии сделать это правильно? Я не уверен как.
Итак, данные как это:
[{
"id": 1,
"user": {
"name": "Nichols Cockle",
"email": "ncockle0@tmall.com",
"address": {
"city": "Turt",
"state": "Thị Trấn Yên Phú"
}
},
"product": {
"name": "Lychee - Canned",
"code": "36987-1526"
}
}, {
"id": 2,
"user": {
"name": "Isacco Scrancher",
"email": "iscrancher1@aol.com",
"address": {
"city": "Likwatang Timur",
"state": "Biharamulo"
}
},
"product": {
"name": "Beer - Original Organic Lager",
"code": "47993-200"
}
}, {
"id": 3,
"user": {
"name": "Elga Sikora",
"email": "esikora2@statcounter.com",
"address": {
"city": "Wenheng",
"state": "Piedra del Águila"
}
},
"product": {
"name": "Parsley - Dried",
"code": "36987-1632"
}
}, {
"id": 4,
"user": {
"name": "Andria Keatch",
"email": "akeatch3@salon.com",
"address": {
"city": "Arras",
"state": "Iracemápolis"
}
},
"product": {
"name": "Wine - Segura Viudas Aria Brut",
"code": "51079-385"
}
}, {
"id": 5,
"user": {
"name": "Dara Sprowle",
"email": "dsprowle4@slate.com",
"address": {
"city": "Huatai",
"state": "Kaduna"
}
},
"product": {
"name": "Pork - Hock And Feet Attached",
"code": "0054-8648"
}
}]
Где это один законченный объект:
{
"id": 1,
"user": {
"name": "Nichols Cockle",
"email": "ncockle0@tmall.com",
"address": {
"city": "Turt",
"state": "Thị Trấn Yên Phú"
}
},
"product": {
"name": "Lychee - Canned",
"code": "36987-1526"
}
}
И каждый файл будет определенным количеством таких объектов.
2 ответа
[РЕДАКТИРОВАТЬ: Этот ответ был пересмотрен в соответствии с пересмотром вопроса.]
Ключом к использованию jq для решения проблемы является -c
опция командной строки, которая производит вывод в формате JSON-Lines (т. е. в данном случае по одному объекту на строку). Затем вы можете использовать инструмент, такой как awk
или же split
распределить эти строки среди нескольких файлов.
Если файл не слишком большой, то самым простым будет начать конвейер с:
jq -c '.[]' INPUTFILE
Если файл слишком велик для удобного размещения в памяти, вы можете использовать потоковый анализатор jq, например, так:
jq -cn --stream 'fromstream(1|truncate_stream(inputs))'
Для дальнейшего обсуждения потокового парсера см., Например, соответствующий раздел в FAQ по jq: https://github.com/stedolan/jq/wiki/FAQ
Разметка
Для различных подходов к разделению вывода, полученного на первом шаге, смотрите, например, Как разбить большой текстовый файл на меньшие файлы с равным количеством строк?
Если требуется, чтобы каждый из выходных файлов был массивом объектов, то, вероятно, я бы использовал awk
выполнить как разделение, так и восстановление за один шаг, но есть много других разумных подходов.
Если вход представляет собой последовательность объектов JSON
Для справки: если исходный файл состоит из потока или последовательности объектов JSON, соответствующий вызов будет:
jq -n -c inputs INPUTFILE
С помощью inputs
таким образом позволяет эффективно обрабатывать произвольное количество объектов.
Можно разрезать файл или поток json с помощью
jq
. См. Сценарий ниже. В
sliceSize
Параметр устанавливает размер срезов и определяет, сколько входов одновременно хранится в памяти. Это позволяет контролировать использование памяти.
Вход для нарезки
Вход не нужно форматировать.
В качестве ввода возможны:
- массив входных данных Json
- поток входных данных Json
Нарезанный вывод
Файлы могут быть созданы с помощью форматированного или компактного Json.
Нарезанные выходные файлы могут содержать:
- массив входных данных Json с размером = $ sliceSize
- поток входных данных Json с элементами $ sliceSize
Представление
Быстрый тест показывает время и потребление памяти во время нарезки (измерено на моем ноутбуке)
файл со 100000 json-объектов, 46 МБ
- sliceSize = 5.000: время = 35 секунд
- sliceSize = 10.000: время = 40 секунд
- sliceSize = 25,000: время = 1 мин.
- sliceSize = 50.000: время = 1 мин 52 сек
файл с 1.000.000 json-объектов, 450МБ
- sliceSize = 5000: время = 5 мин 45 сек
- sliceSize = 10.000: время = 6 мин. 51 сек.
- sliceSize = 25,000: время = 10 мин 5 сек
- sliceSize = 50.000: время = 18 мин 46 сек, максимальное потребление памяти: ~150 МБ
- sliceSize = 100.000: время = 46 мин 25 сек, максимальное потребление памяти: ~ 300 МБ
#!/bin/bash
SLICE_SIZE=2
JQ_SLICE_INPUTS='
2376123525 as $EOF | # random number that does not occur in the input stream to mark the end of the stream
foreach (inputs, $EOF) as $input
(
# init state
[[], []]; # .[0]: array to collect inputs
# .[1]: array that has collected $sliceSize inputs and is ready to be extracted
# update state
if .[0] | length == $sliceSize # enough inputs collected
or $input == $EOF # or end of stream reached
then [[$input], .[0]] # create new array to collect next inputs. Save array .[0] with $sliceSize inputs for extraction
else [.[0] + [$input], []] # collect input, nothing to extract after this state update
end;
# extract from state
if .[1] | length != 0
then .[1] # extract array that has collected $sliceSize inputs
else empty # nothing to extract right now (because still collecting inputs into .[0])
end
)
'
write_files() {
local FILE_NAME_PREFIX=$1
local FILE_COUNTER=0
while read line; do
FILE_COUNTER=$((FILE_COUNTER + 1))
FILE_NAME="${FILE_NAME_PREFIX}_$FILE_COUNTER.json"
echo "writing $FILE_NAME"
jq '.' > $FILE_NAME <<< "$line" # array of formatted json inputs
# jq -c '.' > $FILE_NAME <<< "$line" # compact array of json inputs
# jq '.[]' > $FILE_NAME <<< "$line" # stream of formatted json inputs
# jq -c '.[]' > $FILE_NAME <<< "$line" # stream of compact json inputs
done
}
echo "how to slice a stream of json inputs"
jq -n '{id: (range(5) + 1), a:[1,2]}' | # create a stream of json inputs
jq -n -c --argjson sliceSize $SLICE_SIZE "$JQ_SLICE_INPUTS" |
write_files "stream_of_json_inputs_sliced"
echo -e "\nhow to slice an array of json inputs"
jq -n '[{id: (range(5) + 1), a:[1,2]}]' | # create an array of json inputs
jq -n --stream 'fromstream(1|truncate_stream(inputs))' | # remove outer array to create stream of json inputs
jq -n -c --argjson sliceSize $SLICE_SIZE "$JQ_SLICE_INPUTS" |
write_files "array_of_json_inputs_sliced"
вывод скрипта
how to slice a stream of json inputs
writing stream_of_json_inputs_sliced_1.json
writing stream_of_json_inputs_sliced_2.json
writing stream_of_json_inputs_sliced_3.json
how to slice an array of json inputs
writing array_of_json_inputs_sliced_1.json
writing array_of_json_inputs_sliced_2.json
writing array_of_json_inputs_sliced_3.json
сгенерированные файлы
array_of_json_inputs_sliced_1.json
[
{
"id": 1,
"a": [1,2]
},
{
"id": 2,
"a": [1,2]
}
]
array_of_json_inputs_sliced_2.json
[
{
"id": 3,
"a": [1,2]
},
{
"id": 4,
"a": [1,2]
}
]
array_of_json_inputs_sliced_3.json
[
{
"id": 5,
"a": [1,2]
}
]