如何使用 JQ 将对象列表展开为非规范化对象?
How to use JQ to unroll a list of objects into denormalized objects?
我有以下 JSON 行示例:
{"toplevel_key": "top value 1", "list": [{"key1": "value 1", "key2": "value 2"},{"key1": "value 3", "key2": "value 4"}]}
{"toplevel_key": "top value 2", "list": [{"key1": "value 5", "key2": "value 6"}]}
我想使用 JQ 转换它,将列表展开为固定数量的 "columns",最后得到一个平面 JSON 对象的列表,格式如下:
{
"top-level-key": "top value 1",
"list_0_key1": "value 1",
"list_0_key2": "value 2",
"list_1_key1": "value 3",
"list_1_key2": "value 4",
}
{
"top-level-key": "top value 2",
"list_0_key1": "value 4",
"list_0_key2": "value 5",
"list_1_key1": "",
"list_1_key2": "",
}
注意:实际上我希望每行一个,为了便于阅读而在此处设置格式。
我能够获得我想要的输出的唯一方法是写出我的 JQ 表达式中的所有列:
$ cat example.jsonl | jq -c '{toplevel_key, list_0_key1: .list[0].key1, list_0_key2: .list[0].key2, list_1_key1: .list[1].key1, list_1_key2: .list[1].key2}'
这得到了我想要的结果,但我必须手动编写所有固定的 "columns"(在生产中它会比这多得多)。
我知道我可以使用脚本生成该 JQ 代码,但我不 对这样的解决方案感兴趣 -- 它不会解决我的问题,因为这是针对仅接受 JQ 的应用程序。
有没有办法在纯 JQ 中做到这一点?
这是我到目前为止能够得到的:
$ cat example.jsonl | jq -c '(.list | to_entries | map({("list_" + (.key | tostring)): .value})) | add'
{"list_0":{"key1":"value 1","key2":"value 2"},"list_1":{"key1":"value 3","key2":"value 4"}}
{"list_0":{"key1":"value 5","key2":"value 6"}}
以下是构建它的方法:
{ "top-level-key": .toplevel_key } + ([
range(.list|length) as $i
| .list[$i]
| to_entries[]
| .key = "list_\($i)_\(.key)"
] | from_entries)
这将为每个对应的列表条目映射。
{
"top-level-key": "top value 1",
"list_0_key1": "value 1",
"list_0_key2": "value 2",
"list_1_key1": "value 3",
"list_1_key2": "value 4"
}
{
"top-level-key": "top value 2",
"list_0_key1": "value 5",
"list_0_key2": "value 6"
}
如果您需要将其填充,则必须仔细分析结果以确定实际需要多少并添加填充。但我暂时保留它。
只要知道具体按键的名称,Jeff 的回答就很好。这是一个没有对特定键名进行硬编码的答案,也就是说,它适用于任何结构和嵌套级别的对象:
[leaf_paths as $path | {
"key": $path | map(tostring) | join("_"),
"value": getpath($path)
}] | from_entries
解释:paths
是一个内置函数,它递归地输出一个数组,表示您传递给它的输入的每个元素的位置:所述数组中的元素是有序的键名称和索引导致请求的数组元素。 leaf_paths
是它的一个版本,它只获取到 "leaf" 元素的路径,即不包含其他元素的元素。
澄清一下,给定输入 [[1, 2]]
,paths
将输出 [0], [0, 0], [0, 1]
(即 [1, 2]
、1
和 [= 的路径19=], 分别) 而 leaf_paths
只会输出 [0, 0], [0, 1]
.
这是最难的部分。之后,我们将每个路径作为 $path
(形式为 ["list", 1, "key2"]
)将其每个元素转换为使用 map(tostring)
的字符串表示形式(这给了我们 ["list", "1", "key2"]
)和 join
它们带有下划线。我们将其作为我们要创建的对象中 "entry" 的键:作为值,我们在给定的 $path
处获得原始对象的值。
最后,我们使用 from_entries
将键值对数组转换为 JSON 对象。这将为我们提供类似于 Jeff 答案的输出:即,其中仅出现具有值的键。
但是,您的原始问题要求出现在任何输入对象上的值出现在所有输出中,当输入中缺少相应的值时,相应的值设置为空字符串。这是一个执行此操作的 jq 程序:正如 Jeff 在他的回答中所说,您需要 slurp (-s
) 所有输入值才能使其成为可能:
(map(leaf_paths) | unique) as $paths |
map([$paths[] as $path | {
"key": $path | map(tostring) | join("_"),
"value": (getpath($path) // "")
}] | from_entries)[]
你会注意到它与第一个程序非常相似:主要区别在于我们将 slurped 对象中的所有唯一路径作为 $paths
,并且对于每个对象,我们都经过这些路径而不是继续通过该对象的路径。我们还使用替代运算符 (//
) 将缺失值设置为空字符串。
希望对您有所帮助!
如果您想将 toplevel_key 与列表作为单独行上的字符串连接,您可以使用以下内容:
jq -r '"\(.toplevel_key) - " as $i | [.list | to_entries[] | "\(.value | .key1), \(.value | .key2)"] | join(", ") as $j | $i + $j' toplevel.json
这将提供以下结果:
top value 1 - value 1, value 2, value 3, value 4
top value 2 - value 5, value 6
我有以下 JSON 行示例:
{"toplevel_key": "top value 1", "list": [{"key1": "value 1", "key2": "value 2"},{"key1": "value 3", "key2": "value 4"}]}
{"toplevel_key": "top value 2", "list": [{"key1": "value 5", "key2": "value 6"}]}
我想使用 JQ 转换它,将列表展开为固定数量的 "columns",最后得到一个平面 JSON 对象的列表,格式如下:
{
"top-level-key": "top value 1",
"list_0_key1": "value 1",
"list_0_key2": "value 2",
"list_1_key1": "value 3",
"list_1_key2": "value 4",
}
{
"top-level-key": "top value 2",
"list_0_key1": "value 4",
"list_0_key2": "value 5",
"list_1_key1": "",
"list_1_key2": "",
}
注意:实际上我希望每行一个,为了便于阅读而在此处设置格式。
我能够获得我想要的输出的唯一方法是写出我的 JQ 表达式中的所有列:
$ cat example.jsonl | jq -c '{toplevel_key, list_0_key1: .list[0].key1, list_0_key2: .list[0].key2, list_1_key1: .list[1].key1, list_1_key2: .list[1].key2}'
这得到了我想要的结果,但我必须手动编写所有固定的 "columns"(在生产中它会比这多得多)。
我知道我可以使用脚本生成该 JQ 代码,但我不 对这样的解决方案感兴趣 -- 它不会解决我的问题,因为这是针对仅接受 JQ 的应用程序。
有没有办法在纯 JQ 中做到这一点?
这是我到目前为止能够得到的:
$ cat example.jsonl | jq -c '(.list | to_entries | map({("list_" + (.key | tostring)): .value})) | add'
{"list_0":{"key1":"value 1","key2":"value 2"},"list_1":{"key1":"value 3","key2":"value 4"}}
{"list_0":{"key1":"value 5","key2":"value 6"}}
以下是构建它的方法:
{ "top-level-key": .toplevel_key } + ([
range(.list|length) as $i
| .list[$i]
| to_entries[]
| .key = "list_\($i)_\(.key)"
] | from_entries)
这将为每个对应的列表条目映射。
{
"top-level-key": "top value 1",
"list_0_key1": "value 1",
"list_0_key2": "value 2",
"list_1_key1": "value 3",
"list_1_key2": "value 4"
}
{
"top-level-key": "top value 2",
"list_0_key1": "value 5",
"list_0_key2": "value 6"
}
如果您需要将其填充,则必须仔细分析结果以确定实际需要多少并添加填充。但我暂时保留它。
只要知道具体按键的名称,Jeff 的回答就很好。这是一个没有对特定键名进行硬编码的答案,也就是说,它适用于任何结构和嵌套级别的对象:
[leaf_paths as $path | {
"key": $path | map(tostring) | join("_"),
"value": getpath($path)
}] | from_entries
解释:paths
是一个内置函数,它递归地输出一个数组,表示您传递给它的输入的每个元素的位置:所述数组中的元素是有序的键名称和索引导致请求的数组元素。 leaf_paths
是它的一个版本,它只获取到 "leaf" 元素的路径,即不包含其他元素的元素。
澄清一下,给定输入 [[1, 2]]
,paths
将输出 [0], [0, 0], [0, 1]
(即 [1, 2]
、1
和 [= 的路径19=], 分别) 而 leaf_paths
只会输出 [0, 0], [0, 1]
.
这是最难的部分。之后,我们将每个路径作为 $path
(形式为 ["list", 1, "key2"]
)将其每个元素转换为使用 map(tostring)
的字符串表示形式(这给了我们 ["list", "1", "key2"]
)和 join
它们带有下划线。我们将其作为我们要创建的对象中 "entry" 的键:作为值,我们在给定的 $path
处获得原始对象的值。
最后,我们使用 from_entries
将键值对数组转换为 JSON 对象。这将为我们提供类似于 Jeff 答案的输出:即,其中仅出现具有值的键。
但是,您的原始问题要求出现在任何输入对象上的值出现在所有输出中,当输入中缺少相应的值时,相应的值设置为空字符串。这是一个执行此操作的 jq 程序:正如 Jeff 在他的回答中所说,您需要 slurp (-s
) 所有输入值才能使其成为可能:
(map(leaf_paths) | unique) as $paths |
map([$paths[] as $path | {
"key": $path | map(tostring) | join("_"),
"value": (getpath($path) // "")
}] | from_entries)[]
你会注意到它与第一个程序非常相似:主要区别在于我们将 slurped 对象中的所有唯一路径作为 $paths
,并且对于每个对象,我们都经过这些路径而不是继续通过该对象的路径。我们还使用替代运算符 (//
) 将缺失值设置为空字符串。
希望对您有所帮助!
如果您想将 toplevel_key 与列表作为单独行上的字符串连接,您可以使用以下内容:
jq -r '"\(.toplevel_key) - " as $i | [.list | to_entries[] | "\(.value | .key1), \(.value | .key2)"] | join(", ") as $j | $i + $j' toplevel.json
这将提供以下结果:
top value 1 - value 1, value 2, value 3, value 4
top value 2 - value 5, value 6