使用 jq 如何将一个非常大的 JSON 文件拆分为多个文件,每个文件都有特定数量的对象?
Using jq how can I split a very large JSON file into multiple files, each a specific quantity of objects?
我有一个很大的 JSON 文件,我猜有 400 万个对象。每个顶级都有几个嵌套在里面的级别。我想将其拆分为多个文件,每个文件包含 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"
}
}
每个文件都是指定数量的对象。
[编辑:此答案已根据问题的修订进行了修订。]
使用 jq 解决问题的关键是 -c
命令行选项,它以 JSON-Lines 格式生成输出(即,在本例中,每行一个对象).然后,您可以使用 awk
或 split
等工具将这些行分发到多个文件中。
如果文件不是太大,那么最简单的方法是启动管道:
jq -c '.[]' INPUTFILE
如果文件太大,无法轻松放入内存,那么您可以使用 jq 的流式解析器,如下所示:
jq -cn --stream 'fromstream(1|truncate_stream(inputs))'
有关流解析器的进一步讨论,请参见例如jq 常见问题解答中的相关部分:https://github.com/stedolan/jq/wiki/FAQ#streaming-json-parser
分区
有关对第一步中产生的输出进行分区的不同方法,请参见示例 How to split a large text file into smaller files with equal number of lines?
如果要求每个输出文件都是一个对象数组,那么我可能会使用awk
一步执行分区和重构,但是有很多其他合理的做法。
如果输入是 JSON 个对象的序列
作为参考,如果原始文件由 JSON 个对象的流或序列组成,则适当的调用将是:
jq -n -c inputs INPUTFILE
以这种方式使用 inputs
可以有效地处理任意多个对象。
可以使用 jq
分割 json 文件或流。
请参阅下面的脚本。
sliceSize
参数设置切片的大小并确定有多少输入同时保存在内存中。
这允许控制内存使用。
要切片的输入
输入不必格式化。
可以输入:
- Json 个输入数组
- Json 个输入流
切片输出
可以使用格式化或紧凑的方式创建文件Json
切片输出文件可以包含:
- Json 个输入数组,大小=$sliceSize
- 具有 $sliceSize 项的 Json 个输入流
性能
快速基准测试显示切片期间的时间和内存消耗(在我的笔记本电脑上测量)
包含 100.000 个 json 个对象的文件,46MB
- sliceSize=5.000 : 时间=35 秒
- sliceSize=10.000 : 时间=40 秒
- sliceSize=25.000 : 时间=1 分钟
- sliceSize=50.000 : 时间=1 分 52 秒
包含 1.000.000 个 json 个对象的文件,450MB
- sliceSize=5000 : 时间=5 分 45 秒
- sliceSize=10.000 : 时间=6 分 51 秒
- sliceSize=25.000 : 时间=10 分 5 秒
- sliceSize=50.000:时间=18 分 46 秒,最大内存消耗:~150 MB
- sliceSize=100.000:时间=46 分 25 秒,最大内存消耗:~300 MB
#!/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=
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]
}
]
我有一个很大的 JSON 文件,我猜有 400 万个对象。每个顶级都有几个嵌套在里面的级别。我想将其拆分为多个文件,每个文件包含 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"
}
}
每个文件都是指定数量的对象。
[编辑:此答案已根据问题的修订进行了修订。]
使用 jq 解决问题的关键是 -c
命令行选项,它以 JSON-Lines 格式生成输出(即,在本例中,每行一个对象).然后,您可以使用 awk
或 split
等工具将这些行分发到多个文件中。
如果文件不是太大,那么最简单的方法是启动管道:
jq -c '.[]' INPUTFILE
如果文件太大,无法轻松放入内存,那么您可以使用 jq 的流式解析器,如下所示:
jq -cn --stream 'fromstream(1|truncate_stream(inputs))'
有关流解析器的进一步讨论,请参见例如jq 常见问题解答中的相关部分:https://github.com/stedolan/jq/wiki/FAQ#streaming-json-parser
分区
有关对第一步中产生的输出进行分区的不同方法,请参见示例 How to split a large text file into smaller files with equal number of lines?
如果要求每个输出文件都是一个对象数组,那么我可能会使用awk
一步执行分区和重构,但是有很多其他合理的做法。
如果输入是 JSON 个对象的序列
作为参考,如果原始文件由 JSON 个对象的流或序列组成,则适当的调用将是:
jq -n -c inputs INPUTFILE
以这种方式使用 inputs
可以有效地处理任意多个对象。
可以使用 jq
分割 json 文件或流。
请参阅下面的脚本。
sliceSize
参数设置切片的大小并确定有多少输入同时保存在内存中。
这允许控制内存使用。
要切片的输入
输入不必格式化。
可以输入:
- Json 个输入数组
- Json 个输入流
切片输出
可以使用格式化或紧凑的方式创建文件Json
切片输出文件可以包含:
- Json 个输入数组,大小=$sliceSize
- 具有 $sliceSize 项的 Json 个输入流
性能
快速基准测试显示切片期间的时间和内存消耗(在我的笔记本电脑上测量)
包含 100.000 个 json 个对象的文件,46MB
- sliceSize=5.000 : 时间=35 秒
- sliceSize=10.000 : 时间=40 秒
- sliceSize=25.000 : 时间=1 分钟
- sliceSize=50.000 : 时间=1 分 52 秒
包含 1.000.000 个 json 个对象的文件,450MB
- sliceSize=5000 : 时间=5 分 45 秒
- sliceSize=10.000 : 时间=6 分 51 秒
- sliceSize=25.000 : 时间=10 分 5 秒
- sliceSize=50.000:时间=18 分 46 秒,最大内存消耗:~150 MB
- sliceSize=100.000:时间=46 分 25 秒,最大内存消耗:~300 MB
#!/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=
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]
}
]