shell jq获取值或默认值如果嵌套键不存在

o7jaxewo  于 2022-12-19  发布在  Shell
关注(0)|答案(3)|浏览(173)

我想创建一个类似jq的小函数,它执行get-or-default-if-not-present,类似于Python的dict.get(key, default),这是我们想要的行为:

% echo '{"nested": {"key": "value", "tricky": null}}' > file.json

% my-jq nested.key \"default\" file.json
"value"
% my-jq nested.tricky \"default\" file.json
null
% my-jq nested.dne \"default\" file.json
"default"

我试过使用this answer to a similar question,但是它对嵌套键不起作用。有人有什么建议吗?

function my-jq () {                                                         
  jq --arg key "$1" --arg default "$2" \
  'if has($key) then .[$key] else $default | fromjson end' "$3"
}
7y4bm7vi

7y4bm7vi1#

基于你对"类jq"函数的预测,可以合理地假设你的参数不仅应该被看作路径表达式,而且应该被看作一个通用的jq过滤器,也就是代码,在当前版本的jq中,没有选项或其他速记方法可以像--arg--argjson对数据那样对代码有效。
然而,您可以通过jq的库/模块系统导入代码片段,但对于手头的任务,您需要将代码从函数的参数存储到(临时)文件,在实际jq过滤器的静态部分引用它,之后删除该文件,这不仅繁琐,而且在模块文件中也需要一些静态开销,这不可避免地打开了标注为"代码注入"的潘多拉盒子,因此您也可以屈服于释放出来的邪恶,并使用以下文字动态地组成实际的jq过滤器(并且可能是恶意的)参数内容。(注意,这假设jq表达式有效,因此使用前面带点的.nested.key等):

function my-jq() { jq "($1) // ($2)" "$3"; }
% my-jq .nested.key \"default\" file.json
"value"

my-jq .nested.tricky \"default\" file.json  # fails
"default"

% my-jq .nested.dne \"default\" file.json
"default"

这种最小化方法使用了替代运算符//,它不能给出一个真实但错误的值("null"或"false"),除了空流之外(缺失值)。为了抵消这一点,您可以在基本文档的所有paths中检查path输入是否存在,这将极大地减少您的函数所接受的过滤器种类(考虑到您的用例,这甚至可能被认为是一件好事,但恶意注入仍然是可能的),并且与所有路径进行比较可能会对具有复杂结构的基本文档带来性能损失,但它满足您的三个测试用例:
一个二个一个一个
最后,通过组合两种方法可以减轻潜在的性能损失,即对于一般情况以较快的第一种方法开始,但是如果第一种方法产生不明确的错误值,则返回到可能较慢的第二种方法:

function my-jq() { jq "($1) // if any(path($1) == paths; .) then ($1) else ($2) end" "$3"; }
% my-jq .nested.key \"default\" file.json
"value"

my-jq .nested.tricky \"default\" file.json
null

% my-jq .nested.dne \"default\" file.json
"default"
nbewdwxp

nbewdwxp2#

你不能使用点线路径作为键序列。但是你可以将这样的路径转换成有效的路径以供getpath函数使用。(可能有一个更干净、更健壮的方法来实现这一点。)//运算符提供了一个替代值,如果左边产生falsenull

$ jq  'getpath($key | ltrimstr(".") | split(".")) // $default' file.json --arg key .nested.key --arg default foobar
"value"
$ jq  'getpath($key | ltrimstr(".") | split(".")) // $default' file.json --arg key .nested.dne --arg default foobar
"foobar"

$key | ltrimstr(".") | split(".")首先去掉前面的.,然后在剩余的.上拆分剩余的字符串,以产生单独的键的列表。getpath使用该键的列表产生过滤器; getpath(["nested", "key"])等效于(AFAIK).nested.key

mftmpeh8

mftmpeh83#

您的需求有点违背JSON的规则,但这里有一个可能的解决方案,或者说是一系列解决方案的基础。
此特定解决方案假定路径以数组形式给出:

function my-jq () {
  jq -c --argjson key "$1" --arg default "$2" '
     first(tostream | select(length == 2 and .[0] == $key)) // null
     | if . then .[1] else $default end
  ' 
}

示例:

echo '{"nested": {"key": "value", "tricky": null}}' | my-jq2 '["nested","key"]' haha
"value"

echo '{"nested": {"key": "value", "tricky": null}}' | my-jq2 '["nested","nokey"]' haha
"haha"

echo '{"nested": {"key": "value", "tricky": null}}' | my-jq2 '["nested","tricky"]' haha
null

相关问题