使用 jq 从任意嵌套的 json 数据中提取所有唯一键
Extract all unique keys from arbitrarily nested json data with jq
如主题所述,我的目标是编写一个 all_keys
函数以从任意嵌套的 json blob 中提取所有键,根据需要遍历包含的数组和对象,并输出包含的数组密钥,没有重复项。
例如,给定以下输入:
[
{"name": "/", "children": [
{"name": "/bin", "children": [
{"name": "/bin/ls", "children": []},
{"name": "/bin/sh", "children": []}]},
{"name": "/home", "children": [
{"name": "/home/stephen", "children": [
{"name": "/home/stephen/jq", "children": []}]}]}]},
{"name": "/", "children": [
{"name": "/bin", "children": [
{"name": "/bin/ls", "children": []},
{"name": "/bin/sh", "children": []}]},
{"name": "/home", "children": [
{"name": "/home/stephen", "children": [
{"name": "/home/stephen/jq", "children": []}]}]}]}
]
all_keys
函数应产生以下输出:
[
"children",
"name"
]
为此,我设计了下面的函数,但是它又慢又复杂,所以我想知道你是否能想出一个更简洁、更快的方法来获得相同的结果。
def all_keys:
. as $in |
if type == "object" then
reduce keys[] as $k (
[];
. + [$k, ($in[$k] | all_keys)[]]
) | unique
elif type == "array" then (
reduce .[] as $i (
[];
. + ($i | all_keys)
) | unique
)
else
empty
end
;
作为参考,运行 this 53MB json file 上的功能在我的 Intel T9300@2.50GHz CPU 上大约需要 22 秒(我知道,它很古老但仍然可以正常工作)。
天真的方法只是收集所有键并获取唯一值。
[.. | objects | keys[]] | unique
但是对于这些数据,它有点慢,因为需要收集和排序键。
我们可以在这方面做得更好。由于我们正在尝试确定所有不同的键,因此我们会使用某种哈希图来提高效率。好吧,我们有可以这样做的对象。
reduce (.. | objects | keys[]) as $k ({}; .[$k] = true) | keys
我没有测量这个时间,但它比其他版本快很多。我什至没有等到另一个完成,这个在我的工作机器 (i5-2400@3.1GHz) 上不到 10 秒就完成了。
我认为您会发现 OP all_keys 的以下变体实际上比使用 ..
的版本稍快;这可能是意料之中的——对于 jeopardy.json,..
总共生成 1,731,807 个 JSON 个实体,而只有 216,930 个 JSON 个对象:
def all_keys:
def uniquely(f): reduce f as $x ({}; .[$x] = true) | keys;
def rkeys:
if type == "object" then keys[] as $k | ($k, (.[$k]|rkeys))
elif type == "array" then .[]|rkeys
else empty
end;
uniquely(rkeys);
如主题所述,我的目标是编写一个 all_keys
函数以从任意嵌套的 json blob 中提取所有键,根据需要遍历包含的数组和对象,并输出包含的数组密钥,没有重复项。
例如,给定以下输入:
[
{"name": "/", "children": [
{"name": "/bin", "children": [
{"name": "/bin/ls", "children": []},
{"name": "/bin/sh", "children": []}]},
{"name": "/home", "children": [
{"name": "/home/stephen", "children": [
{"name": "/home/stephen/jq", "children": []}]}]}]},
{"name": "/", "children": [
{"name": "/bin", "children": [
{"name": "/bin/ls", "children": []},
{"name": "/bin/sh", "children": []}]},
{"name": "/home", "children": [
{"name": "/home/stephen", "children": [
{"name": "/home/stephen/jq", "children": []}]}]}]}
]
all_keys
函数应产生以下输出:
[
"children",
"name"
]
为此,我设计了下面的函数,但是它又慢又复杂,所以我想知道你是否能想出一个更简洁、更快的方法来获得相同的结果。
def all_keys:
. as $in |
if type == "object" then
reduce keys[] as $k (
[];
. + [$k, ($in[$k] | all_keys)[]]
) | unique
elif type == "array" then (
reduce .[] as $i (
[];
. + ($i | all_keys)
) | unique
)
else
empty
end
;
作为参考,运行 this 53MB json file 上的功能在我的 Intel T9300@2.50GHz CPU 上大约需要 22 秒(我知道,它很古老但仍然可以正常工作)。
天真的方法只是收集所有键并获取唯一值。
[.. | objects | keys[]] | unique
但是对于这些数据,它有点慢,因为需要收集和排序键。
我们可以在这方面做得更好。由于我们正在尝试确定所有不同的键,因此我们会使用某种哈希图来提高效率。好吧,我们有可以这样做的对象。
reduce (.. | objects | keys[]) as $k ({}; .[$k] = true) | keys
我没有测量这个时间,但它比其他版本快很多。我什至没有等到另一个完成,这个在我的工作机器 (i5-2400@3.1GHz) 上不到 10 秒就完成了。
我认为您会发现 OP all_keys 的以下变体实际上比使用 ..
的版本稍快;这可能是意料之中的——对于 jeopardy.json,..
总共生成 1,731,807 个 JSON 个实体,而只有 216,930 个 JSON 个对象:
def all_keys:
def uniquely(f): reduce f as $x ({}; .[$x] = true) | keys;
def rkeys:
if type == "object" then keys[] as $k | ($k, (.[$k]|rkeys))
elif type == "array" then .[]|rkeys
else empty
end;
uniquely(rkeys);