Say I’m getting an API response back that looks somewhat like this:
{
"results": [
{
"name": "foobar",
"description": "it's the foobar 1000 baby!"
},
{
"name": "another_thing",
"description": "acme 1000 whizbang machine"
}
]
}
I pipe this object to the filter .results[] | select(.name == "foobar").description
, but I’m not super confident I’ve written the select()
filter completely correctly. How can I assert that the select()
filter is functioning as intended, and returning exactly 1 result?
5
Answers
The
select()
filter emits a (possibly empty) stream of JSON objects, not a single array of JSON objects. Therefore while we can uselength
to do such an assertion, we have to be clever about it:Of course you can do whatever you like instead of raising the error.
The way this works is by wrapping the stream of objects returned by the
select()
filter in an array, so that the subsequentlength
filter doesn't operate on each object individually and instead operates on the list of objects. If the assertion passes, we unwrap the array again for the caller's convenience with.[]
.If you want an expression which is guaranteed not to produce MORE than one result, use
first/1
.If you want an expression which is guaranteed to produce exactly one result, assuming there are no errors preventing at least one result, you could perhaps use
first
in conjunction with//
. In this case, of course, you’d have to specify what value you wanted in case the underlying query produces no results.Here’s an example:
A simpler alternative that might nevertheless be acceptable is:
There are various functions to filter your input stream in a way that only certain results or a given number of them will be kept while others are being discarded.
To (silently) discard a second and all subsequent results (i.e. getting only the first one or nothing), use
first(f)
as suggested by @peak. For the first n results there is the generalizedlimit($n; f)
filter which provides you with all results until the nth one, i.e a stream of up to n items, thuslimit(1; f)
would act likefirst(f)
. Note that these filters (correctly) produce nothing if the stream was empty in the first place. You can test against this case usingisempty(f)
which produces a boolean as expected.To actively assert an exact (or a minimum or a maximum) number of results, i.e. take action if that fails, without collecting all items into a memory-consuming array just to query its length, you could count the size of the stream using
reduce
, and act upon evaluation of that result. Here’s a function that takes a number, an input stream which is reproduced (unaltered, i.e. as a stream) if the amount matches, and another filter that is produced instead if the counting fails. (Replace==
used for exact matches with one of<
,<=
,>
,>=
to open up the upper or lower bounds.)Applied to your test case:
You can further generalize this approach by e.g. providing the counting result to a custom function.
If you have an array to start with, it’s probably more straightforward to use the
map(select(f))
pattern:alternatively:
Here’s an "assertion"-style filter that you could use to avoid collecting all the results:
Usage example: