如何判断 jq 过滤器是否成功从 JSON 数据结构中提取数据?

How can I tell if a jq filter successfully pulls data from a JSON data structure?

我想知道给定的过滤器是否成功地从 JSON 数据结构中提取数据。例如:

###### For the user steve...

% Name=steve
% jq -j --arg Name "$Name" '.[]|select(.user == $Name)|.value' <<<'
[
   {"user":"steve", "value":false},
   {"user":"tom", "value":true},
   {"user":"pat", "value":null},
   {"user":"jane", "value":""}
]'
false
% echo $?
0

注意:成功的结果可以包括布尔值,null,甚至是空字符串。

###### Now for user not in the JSON data...

% Name=mary
% jq -j --arg Name "$Name" '.[]|select(.user == $Name)|.value' <<<'
[
   {"user":"steve", "value":false},
   {"user":"tom", "value":true},
   {"user":"pat", "value":null},
   {"user":"jane", "value":""}
]'
% echo $?
0

如果过滤器不从 JSON 数据结构中提取数据,我需要知道这一点。我更喜欢过滤器而不是 return 非零 return 代码。

我将如何确定选择器是成功地从 JSON 数据结构中提取数据还是未能提取数据?

重要提示:上面的过滤器只是一个例子,该解决方案需要适用于任何 jq 过滤器。

注:评测环境为Bash4.2+.

您可以使用 jq Manual 中的 -e / --exit-status 标志,它表示

Sets the exit status of jq to 0 if the last output values was neither false nor null, 1 if the last output value was either false or null, or 4 if no valid result was ever produced. Normally jq exits with 2 if there was any usage problem or system error, 3 if there was a jq program compile error, or 0 if the jq program ran.

我可以用下面的基本过滤器来演示用法,因为你给出的例子对我不起作用。

查询成功,

dudeOnMac:~$ jq -e '.foo?' <<< '{"foo": 42, "bar": "less interesting data"}'
42
dudeOnMac:~$ echo $?
0

对于无效查询,使用不存在的实体完成zoo

dudeOnMac:~$ jq -e '.zoo?' <<< '{"foo": 42, "bar": "less interesting data"}'
null
dudeOnMac:~$ echo $?
1

对于错误场景,返回我通过双引号 jq 输入流创建的代码 2

dudeOnMac:~$ jq -e '.zoo?' <<< "{"foo": 42, "bar": "less interesting data"}"
jq: error: Could not open file interesting: No such file or directory
jq: error: Could not open file data}: No such file or directory
dudeOnMac:~$ echo $?
2

我找到了满足我所有要求的解决方案!请告诉我您的想法!

我们的想法是使用 jq -e "$Filter" 作为第一遍检查。然后对于 1 的 return 代码,进行 jq "path($Filter)" 检查。后者只有在事实上存在进入 JSON 数据的路径时才会成功。

Select.sh

#!/bin/bash

Select()
{
   local Name=""
   local Filter=""
   local Input=""
   local Result Status

   Result="$(jq -e --arg Name "$Name" "$Filter" <<<"$Input")"
   Status=$?

   case $Status in
   1) jq --arg Name "$Name" "path($Filter)" <<<"$Input" >/dev/null 2>&1
      Status=$?
      ;;
   *) ;;
   esac

   [[ $Status -eq 0 ]] || Result="***ERROR***"
   echo "$Status $Result"
}

Filter='.[]|select(.user == $Name)|.value'
Input='[
   {"user":"steve", "value":false},
   {"user":"tom", "value":true},
   {"user":"pat", "value":null},
   {"user":"jane", "value":""}
]'

Select steve "$Filter" "$Input"
Select tom   "$Filter" "$Input"
Select pat   "$Filter" "$Input"
Select jane  "$Filter" "$Input"
Select mary  "$Filter" "$Input"

以及上面的执行:

% ./Select.sh
0 false
0 true
0 null
0 ""
4 ***ERROR***

鉴于 jq 是这样的,特别是它是面向流的,我倾向于认为更好的方法是定义和使用一个或多个过滤器来进行您想要的区分.因此,与其编写 .a 来访问字段的值,不如编写 get("a") 假设 get/1 定义如下:

def get(f): if has(f) then .[f] else error("\(type) is not defined at \(f)") end;

现在您可以轻松判断对象是否有键,一切准备就绪。 get 的这个定义也可以用于数组。

我在下面添加了一个更新的解决方案

这里的根本问题是,当尝试使用 .key.[key] 语法从对象中检索值时,jq — by definition — can' t 区分丢失的键和值为 null.

的键

您可以改为定义自己的查找函数:

def lookup(k):if has(k) then .[k] else error("invalid key") end;

然后像这样使用它:

$ jq 'lookup("a")' <<<'{}' ; echo $?
jq: error (at <stdin>:1): invalid key
5

$ jq 'lookup("a")' <<<'{"a":null}' ; echo $?
null
0

如果您随后始终使用 lookup 而不是内置方法,我认为这会给您带来您想要的行为。


这是另一种方法,少 bashjq

#!/bin/bash

lib='def value(f):((f|tojson)//error("no such value"))|fromjson;'

users=( steve tom pat jane mary )

Select () {
  local name= filter= input=
  local -i status=0
  result=$( jq --arg name "$name" "${lib}value(${filter})" <<<$input  2>/dev/null )
  status=$? 
  (( status )) && result="***ERROR***"
  printf '%s\t%d %s\n' "$name" $status "$result"
}

filter='.[]|select(.user == $name)|.value'

input='[{"user":"steve","value":false},
        {"user":"tom","value":true},
        {"user":"pat","value":null},
        {"user":"jane","value":""}]'

for name in "${users[@]}"
do
  Select "$name" "$filter" "$input"
done

这会产生输出:

steve   0 false
tom     0 true
pat     0 null
jane    0 ""
mary    5 ***ERROR***

这利用了这样一个事实,即过滤器没有输入,就像 empty,空的将触发 // 的替代,但字符串 — 如 "null""false" — 不会。

应该注意 value/1 不适用于简单的过滤器 key/index 查找 objects/arrays,但您的解决方案也不会。我有理由相信 涵盖所有情况,你需要这样的东西(或你的)andgetlookup.