json 我如何Assert一个给定的jq表达式只返回一个结果?

xtfmy6hx  于 2023-05-19  发布在  其他
关注(0)|答案(5)|浏览(194)

假设我得到了一个API响应,它看起来有点像这样:

{
  "results": [
    {
      "name": "foobar",
      "description": "it's the foobar 1000 baby!"
    },
    {
      "name": "another_thing",
      "description": "acme 1000 whizbang machine"
    }
  ]
}

我将这个对象通过管道传递给过滤器.results[] | select(.name == "foobar").description,但我并不十分确信我已经完全正确地编写了select()过滤器。如何Assertselect()过滤器按预期运行,并返回1个结果?

1qczuiv0

1qczuiv01#

如果你有一个数组,使用map(select(f))模式可能更简单:

.results
| map(select(.name == "foobar") | .description)
| if length == 1 then first else error("expected exactly one result") end

或者:

.results
| map(select(.name == "foobar") | .description)
| select(length == 1)[0] // error("expected exactly one result")
dw1jzc5e

dw1jzc5e2#

select()过滤器发出一个(可能是空的)JSON对象流,* 而不是 * 一个JSON对象数组。因此,虽然我们可以使用length来做这样的Assert,但我们必须巧妙地使用它:

[select(.name == "foobar").description]  | if length == 1 then .[] else "\(length) results produced" | error end

当然,你可以做任何你喜欢的事情,而不是提高错误。
其工作方式是将select()过滤器返回的对象流 Package 在一个数组中,这样后续的length过滤器就不会单独对每个对象进行操作,而是对对象列表进行操作。如果Assert通过,为了方便调用者,我们使用.[]再次展开数组。

xytpbqjk

xytpbqjk3#

有各种函数可以过滤输入流,只保留某些结果或给定数量的结果,而丢弃其他结果。
(静默地)丢弃第二个和所有后续结果(即只得到第一个或什么都不得到),使用first(f)作为suggested by @peak。对于前n个结果,有一个广义的limit($n; f)过滤器,它为您提供所有结果,直到第n个,即最多n个项目的流,因此limit(1; f)将像first(f)一样。请注意,如果流一开始是空的,这些过滤器(正确地)什么也不产生。您可以使用isempty(f)来测试这种情况,它会产生一个预期的布尔值。
要主动Assert精确(或最小或最大)数量的结果,即如果失败了就采取行动,而不是将所有项收集到一个消耗内存的数组中,只是为了查询它的长度,您可以使用reduce计算流的大小,并根据结果的评估采取行动。这里有一个函数,它接受一个数字,一个被复制的输入流(未改变,即作为流),而如果计数失败则产生另一个过滤器。(将用于精确匹配的==替换为<<=>>=之一,以打开上限或下限。

def assert_n($n; f; g): if reduce f as $_ (0; .+1) == $n then f else g end;

应用于您的测试用例:

# produces exactly one result, namely "it's the foobar 1000 baby!"
assert_n(1; .results[] | select(.name == "foobar").description; error("not one"))

# errors out as select(true) happens to produce too many (>1) results
assert_n(1; .results[] | select(true).description; error("not one"))

# errors out as select(false) happens to produce too few (<1) results
assert_n(1; .results[] | select(false).description; error("not one"))

你可以进一步推广这种方法,例如:将所述计数结果提供给定制函数。

def assert_c(c; f; g): if reduce f as $_ (0; .+1) | c then f else g end;

# produces an odd number of results, or errors out otherwise
assert_c(.%2 == 1; .results[] | select(.name == "foobar"); error("was even"))
qni6mghb

qni6mghb4#

如果你想要一个保证不会产生多个结果的表达式,使用first/1
如果你想要一个保证只产生一个结果的表达式,假设没有错误阻止至少一个结果,你可能可以将first//结合使用。当然,在这种情况下,您必须指定所需的值,以防底层查询没有结果。
下面是一个例子:

first(select(.name == "foobar" and has("description"))).description // "no description provided"

一个可能仍然可以接受的更简单的替代方案是:

first(select(.name == "foobar").description // empty) // "no description provided"
vqlkdk9b

vqlkdk9b5#

这里有一个“Assert”风格的过滤器,您可以使用它来避免收集所有结果:

def assert_one(s):
  reduce s as $x (null;
    if . == null then {emit: $x, n:1}
    else "assertion error: more than one"|error
    end )
  | select(.n == 1).emit 
  // ("assertion error: none"|error);

使用示例:

[{ name: "foobar", description: "yes"},
 { name: "foobar", description: "maybe"} ]
| assert_one(.[] | select(.name == "foobar" and has("description")).description)

相关问题