使用 jq 展平嵌套 JSON
Flatten nested JSON using jq
我想展平嵌套的 json 对象,例如{"a":{"b":1}}
到 {"a.b":1}
以便在 solr 中消化它。
我有 11 TB 的 json 文件,它们都是嵌套的并且在字段名称中包含点,这意味着 elasticsearch(点)和 solr(没有 _childDocument_
符号的嵌套)都不能将其消化为是。
其他解决方案是将字段名称中的点替换为下划线并将其推送到 elasticsearch,但我对 solr 的体验要好得多,因此我更喜欢扁平化解决方案(除非 solr 可以消化那些嵌套的 json 原样??)。
只有当消化过程比 solr 花费的时间少得多时,我才会更喜欢 elasticsearch,因为我的首要任务是尽可能快地消化(因此我选择了 jq 而不是在 python 中编写脚本)。
请帮忙。
编辑:
我认为这对示例 3 和 4 为我解决了这个问题:
https://lucidworks.com/blog/2014/08/12/indexing-custom-json-data/
我会尽快尝试。
事实证明,curl -XPOST 'http://localhost:8983/solr/flat/update/json/docs' -d @json_file
就是这样做的:
{
"a.b":[1],
"id":"24e3e780-3a9e-4fa7-9159-fc5294e803cd",
"_version_":1535841499921514496
}
编辑 1:solr 6.0.1 与 bin/solr -e cloud
。集合名称为 flat
,其余均为默认值(data-driven-schema
也是默认值)。
编辑 2:我使用的最终脚本:find . -name '*.json' -exec curl -XPOST 'http://localhost:8983/solr/collection1/update/json/docs' -d @{} \;
。
编辑 3:也可以与 xargs 并行并使用 jq 添加 id 字段:find . -name '*.json' -print0 | xargs -0 -n 1 -P 8 -I {} sh -c "cat {} | jq '. + {id: .a.b}' | curl -XPOST 'http://localhost:8983/solr/collection/update/json/docs' -d @-"
其中 -P
是并行因子。我使用 jq 设置了一个 id,因此同一文档的多次上传不会在集合中创建重复项(当我搜索 -P
的最佳值时,它在集合中创建了重复项)
您还可以使用以下 jq 命令以这种方式展平嵌套的 JSON 对象:
[leaf_paths as $path | {"key": $path | join("."), "value": getpath($path)}] | from_entries
它的工作方式是:leaf_paths
returns 数组流,代表给定 JSON 文档中 "leaf elements" 出现的路径,即,没有子元素的元素,例如数字、字符串和布尔值。我们将该流通过管道传输到具有 key
和 value
属性的对象中,其中 key
包含路径数组的元素作为由点连接的字符串,而 value
包含该元素小路。最后,我们将整个东西放在一个数组中,然后 运行 from_entries
放在上面,这会将 {key, value}
对象的数组转换为包含这些键值对的对象。
这只是 Santiago 的 jq 的变体:
. as $in
| reduce leaf_paths as $path ({};
. + { ($path | map(tostring) | join(".")): $in | getpath($path) })
它避免了 key/value 构造和销毁的开销。
(如果你能访问jq 1.5以后的jq版本,你可以省略"map(tostring)"。)
关于这两个 jq 解决方案的两个要点:
数组也被展平了。
例如。给定 {"a": {"b": [0,1,2]}}
作为输入,输出将是:
{
"a.b.0": 0,
"a.b.1": 1,
"a.b.2": 2
}
如果原始 JSON 中的任何键包含句点,则可能会发生键冲突;这种冲突通常会导致价值损失。例如,使用以下输入会发生这种情况:
{"a.b":0, "a": {"b": 1}}
这是一个使用tostream、select、join的解决方案, reduce 和 setpath
reduce ( tostream | select(length==2) | .[0] |= [join(".")] ) as [$p,$v] (
{}
; setpath($p; $v)
)
我最近编写了一个名为 jqg 的脚本,它可以将任意复杂的 JSON 展平并使用正则表达式搜索结果;要简单地压平 JSON,您的正则表达式将是“.
”,它匹配所有内容。与上面的答案不同,该脚本将处理嵌入式数组、false
和 null
值,并且可以选择将空数组和对象 ([]
& {}
) 视为叶节点。
$ jq . test/odd-values.json
{
"one": {
"start-string": "foo",
"null-value": null,
"integer-number": 101
},
"two": [
{
"two-a": {
"non-integer-number": 101.75,
"number-zero": 0
},
"true-boolean": true,
"two-b": {
"false-boolean": false
}
}
],
"three": {
"empty-string": "",
"empty-object": {},
"empty-array": []
},
"end-string": "bar"
}
$ jqg . test/odd-values.json
{
"one.start-string": "foo",
"one.null-value": null,
"one.integer-number": 101,
"two.0.two-a.non-integer-number": 101.75,
"two.0.two-a.number-zero": 0,
"two.0.true-boolean": true,
"two.0.two-b.false-boolean": false,
"three.empty-string": "",
"three.empty-object": {},
"three.empty-array": [],
"end-string": "bar"
}
jqg
使用 jq 1.6
进行测试
注意:我是jqg
脚本的作者。
我想展平嵌套的 json 对象,例如{"a":{"b":1}}
到 {"a.b":1}
以便在 solr 中消化它。
我有 11 TB 的 json 文件,它们都是嵌套的并且在字段名称中包含点,这意味着 elasticsearch(点)和 solr(没有 _childDocument_
符号的嵌套)都不能将其消化为是。
其他解决方案是将字段名称中的点替换为下划线并将其推送到 elasticsearch,但我对 solr 的体验要好得多,因此我更喜欢扁平化解决方案(除非 solr 可以消化那些嵌套的 json 原样??)。
只有当消化过程比 solr 花费的时间少得多时,我才会更喜欢 elasticsearch,因为我的首要任务是尽可能快地消化(因此我选择了 jq 而不是在 python 中编写脚本)。
请帮忙。
编辑:
我认为这对示例 3 和 4 为我解决了这个问题: https://lucidworks.com/blog/2014/08/12/indexing-custom-json-data/
我会尽快尝试。
事实证明,curl -XPOST 'http://localhost:8983/solr/flat/update/json/docs' -d @json_file
就是这样做的:
{
"a.b":[1],
"id":"24e3e780-3a9e-4fa7-9159-fc5294e803cd",
"_version_":1535841499921514496
}
编辑 1:solr 6.0.1 与 bin/solr -e cloud
。集合名称为 flat
,其余均为默认值(data-driven-schema
也是默认值)。
编辑 2:我使用的最终脚本:find . -name '*.json' -exec curl -XPOST 'http://localhost:8983/solr/collection1/update/json/docs' -d @{} \;
。
编辑 3:也可以与 xargs 并行并使用 jq 添加 id 字段:find . -name '*.json' -print0 | xargs -0 -n 1 -P 8 -I {} sh -c "cat {} | jq '. + {id: .a.b}' | curl -XPOST 'http://localhost:8983/solr/collection/update/json/docs' -d @-"
其中 -P
是并行因子。我使用 jq 设置了一个 id,因此同一文档的多次上传不会在集合中创建重复项(当我搜索 -P
的最佳值时,它在集合中创建了重复项)
您还可以使用以下 jq 命令以这种方式展平嵌套的 JSON 对象:
[leaf_paths as $path | {"key": $path | join("."), "value": getpath($path)}] | from_entries
它的工作方式是:leaf_paths
returns 数组流,代表给定 JSON 文档中 "leaf elements" 出现的路径,即,没有子元素的元素,例如数字、字符串和布尔值。我们将该流通过管道传输到具有 key
和 value
属性的对象中,其中 key
包含路径数组的元素作为由点连接的字符串,而 value
包含该元素小路。最后,我们将整个东西放在一个数组中,然后 运行 from_entries
放在上面,这会将 {key, value}
对象的数组转换为包含这些键值对的对象。
这只是 Santiago 的 jq 的变体:
. as $in
| reduce leaf_paths as $path ({};
. + { ($path | map(tostring) | join(".")): $in | getpath($path) })
它避免了 key/value 构造和销毁的开销。
(如果你能访问jq 1.5以后的jq版本,你可以省略"map(tostring)"。)
关于这两个 jq 解决方案的两个要点:
数组也被展平了。 例如。给定
{"a": {"b": [0,1,2]}}
作为输入,输出将是:{ "a.b.0": 0, "a.b.1": 1, "a.b.2": 2 }
如果原始 JSON 中的任何键包含句点,则可能会发生键冲突;这种冲突通常会导致价值损失。例如,使用以下输入会发生这种情况:
{"a.b":0, "a": {"b": 1}}
这是一个使用tostream、select、join的解决方案, reduce 和 setpath
reduce ( tostream | select(length==2) | .[0] |= [join(".")] ) as [$p,$v] (
{}
; setpath($p; $v)
)
我最近编写了一个名为 jqg 的脚本,它可以将任意复杂的 JSON 展平并使用正则表达式搜索结果;要简单地压平 JSON,您的正则表达式将是“.
”,它匹配所有内容。与上面的答案不同,该脚本将处理嵌入式数组、false
和 null
值,并且可以选择将空数组和对象 ([]
& {}
) 视为叶节点。
$ jq . test/odd-values.json
{
"one": {
"start-string": "foo",
"null-value": null,
"integer-number": 101
},
"two": [
{
"two-a": {
"non-integer-number": 101.75,
"number-zero": 0
},
"true-boolean": true,
"two-b": {
"false-boolean": false
}
}
],
"three": {
"empty-string": "",
"empty-object": {},
"empty-array": []
},
"end-string": "bar"
}
$ jqg . test/odd-values.json
{
"one.start-string": "foo",
"one.null-value": null,
"one.integer-number": 101,
"two.0.two-a.non-integer-number": 101.75,
"two.0.two-a.number-zero": 0,
"two.0.true-boolean": true,
"two.0.two-b.false-boolean": false,
"three.empty-string": "",
"three.empty-object": {},
"three.empty-array": [],
"end-string": "bar"
}
jqg
使用 jq 1.6
注意:我是jqg
脚本的作者。