Json 如何从属性展平树并构建路径?

Json 如何从属性展平树并构建路径?,json,recursion,tree,jq,Json,Recursion,Tree,Jq,我的目标是将具有单个文件历史信息的类似文件系统的结构(嵌套目录)扁平化为csv文件以供进一步处理。这就是我迄今为止所尝试的 简化的输入如下所示: { "dirs": [ { "name": "documents", "files": [ { "name": "foo.bar", "history": [ { "hash": "1

我的目标是将具有单个文件历史信息的类似文件系统的结构(嵌套目录)扁平化为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","..."
棘手的部分是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的书,谢谢!