Json 如何从属性展平树并构建路径?
我的目标是将具有单个文件历史信息的类似文件系统的结构(嵌套目录)扁平化为csv文件以供进一步处理。这就是我迄今为止所尝试的 简化的输入如下所示:Json 如何从属性展平树并构建路径?,json,recursion,tree,jq,Json,Recursion,Tree,Jq,我的目标是将具有单个文件历史信息的类似文件系统的结构(嵌套目录)扁平化为csv文件以供进一步处理。这就是我迄今为止所尝试的 简化的输入如下所示: { "dirs": [ { "name": "documents", "files": [ { "name": "foo.bar", "history": [ { "hash": "1
{ "dirs": [
{
"name": "documents",
"files": [
{
"name": "foo.bar",
"history": [
{ "hash": "123", "timestamp": "..."},
{ "hash": "234", "timestamp": "..."}
]
}
],
"subDirs": [
{ "name": "tmp", "files": [...], "subDirs": [...]
}
]
}
]}
"documents","foo.bar","123","..."
"documents","foo.bar","234","..."
"documents","bar.baz","345","..."
"documents","bar.baz","456","..."
"documents/tmp","deleteme","567","..."
"documents/tmp","deleteme","678","..."
棘手的部分是csv文件应该包含完整的目录路径,而不仅仅是目录名。所需的输出如下所示:
{ "dirs": [
{
"name": "documents",
"files": [
{
"name": "foo.bar",
"history": [
{ "hash": "123", "timestamp": "..."},
{ "hash": "234", "timestamp": "..."}
]
}
],
"subDirs": [
{ "name": "tmp", "files": [...], "subDirs": [...]
}
]
}
]}
"documents","foo.bar","123","..."
"documents","foo.bar","234","..."
"documents","bar.baz","345","..."
"documents","bar.baz","456","..."
"documents/tmp","deleteme","567","..."
"documents/tmp","deleteme","678","..."
使用recurse
展平大部分数据使用以下查询:
.dirs[] | recurse(.subDirs[]?) | . as $d | $d.files[]? as $f | $f.history[]? as $h | [$d.name, $f.name, $h.hash, $h.timestamp] | @csv
…但我无法理解如何保留构建目录路径。任何建议都将不胜感激。我认为您需要为此定义一个自定义递归函数,如下所示;它假定所有文件都有非空的
历史记录
def f(pfix):
( [ pfix, .name ] | join("/") ) as $path |
( .files[] | .history[] as $hist | [ $path, .name, $hist[] ] ),
( .subDirs[] | f($path) );
.dirs[] | f(empty) | @csv
我认为您需要为此定义一个自定义递归函数,如下所示;它假定所有文件都有非空的
历史记录
def f(pfix):
( [ pfix, .name ] | join("/") ) as $path |
( .files[] | .history[] as $hist | [ $path, .name, $hist[] ] ),
( .subDirs[] | f($path) );
.dirs[] | f(empty) | @csv
这是一种既不显式使用递归(*)也不依赖递归结构的方法:
def names($path):
reduce getpath($path[0:range(0; $path|length)]) as $v ("";
if $v | type == "object" and has("name") then . + "/" + $v["name"] else . end) ;
paths as $p
| getpath($p) as $v
| select($v | objects | has("history"))
| [names($p), getpath($p + ["name"])]
+ ($v["history"][] | [.hash, .timestamp] )
| @csv
这会产生“绝对”路径(例如“/文档”);省略前导“/”可以很容易地完成
(*)
路径是递归定义的,但其方式利用了jq的尾部调用优化(TCO),该优化仅适用于arity-0递归函数。以下是一种既不显式使用递归(*)也不依赖递归结构的方法:
def names($path):
reduce getpath($path[0:range(0; $path|length)]) as $v ("";
if $v | type == "object" and has("name") then . + "/" + $v["name"] else . end) ;
paths as $p
| getpath($p) as $v
| select($v | objects | has("history"))
| [names($p), getpath($p + ["name"])]
+ ($v["history"][] | [.hash, .timestamp] )
| @csv
这会产生“绝对”路径(例如“/文档”);省略前导“/”可以很容易地完成
(*)路径
是递归定义的,但其方式利用了jq的尾部调用优化(TCO),该优化仅适用于arity-0递归函数。这非常有效。非常感谢你!我的“recurse”@oguzismail有点走到了死胡同——在jqplay的示例数据中,您的解决方案会产生一个错误,可以通过编写.history[]?
来解决这个问题,这非常有效。非常感谢你!我的“recurse”@oguzismail有点走到了死胡同——在jqplay的示例数据中,您的解决方案会产生一个错误,可以通过编写.history[]?
来解决,谢谢您对此的理解。这更容易理解,但我已经用oguz的答案解决了我的问题。我仍在学习jq,所以看不同的解决方案肯定会有帮助。@Buxel考虑到递归的缺点,这可能是一个不同的解决方案,但仍然是一个更好的解决方案performance@oguzismail-对于此类问题,未优化的递归是可以的。只有当递归深度很大时,它才开始起作用。所以基本上路径使用TCO自动优化未优化(手动)的递归?我会读一些关于TCO的书,谢谢!谢谢你对这件事的看法。这更容易理解,但我已经用oguz的答案解决了我的问题。我仍在学习jq,所以看不同的解决方案肯定会有帮助。@Buxel考虑到递归的缺点,这可能是一个不同的解决方案,但仍然是一个更好的解决方案performance@oguzismail-对于此类问题,未优化的递归是可以的。只有当递归深度很大时,它才开始起作用。所以基本上路径使用TCO自动优化未优化(手动)的递归?我会读一些关于TCO的书,谢谢!