I have an input file which type may vary from array to object, one of the following inputs expected:
[{"version": "1.0"}]
{version: "1.0"}
How can I construct jq expression for the output to be always converted into array. I came up with the following:
jq 'if (select(has("version")?)) then [.] else . end'
once version
key is matched, object is added inside array, but if not matched, that would mean it's already an array, nothing is printed, and I would it expect it to be printed as it is. Please suggest the right way to achieve it.
CodePudding user response:
You can check the input's type
directly:
jq 'if type == "object" then [.] else . end'
Or use the deconstruction alternative operator ?//
(available since jq 1.6):
jq '. as [$v] ?// $v | [$v]'
CodePudding user response:
Auxiliary to pmf's answer, why doesn't this work?
input.jsonl
:
[{"version": "1.0"}]
{"version": "2.0"}
Command:
jq -c 'if (select(has("version")?)) then [.] else . end'
Actual output:
[{"version": "2.0"}]
Desired output:
[{"version": "1.0"}]
[{"version": "2.0"}]
First of all, select
filters out values. For each value input to select
, it evaluates the filter you give it on its input, and if that filter evaluates to a true value, the whole input value it emitted unchanged. Otherwise there is no output. Using select
here is unhelpful, because you want a true or false value for the if
condition. If the condition part of your if-then-else
expression emits no output then the whole if-then-else
expression emits no output.
A more thorough way to understand select(expr)
is that every time expr
emits a true value then the select(expr)
emits its input value. A more thorough way to understand if cond then a else b end
is that every time cond
emits a true value the whole if-then-else
emits a
and every time cond
emits a false value the whole if-then-else
emits b
.
Okay, so forget the select
... why doesn't this work?
Command:
jq -c 'if (has("version")?) then [.] else . end'
Actual output:
[{"version": "2.0"}]
In this case, the ?
is the error suppression operator and is equivalent to try has("version")
which is itself equivalent to try has("version") catch empty
. This means that when an error occurs the expression returns empty
which means no output. An error does indeed occur when the input is a list instead of an object. When the condition part of the if-then-else
expression emits no output, you guessed it, the whole expression emits no output.
You could make this work by doing this instead:
Command:
jq -c 'if (try has("version") catch false) then [.] else . end'
Actual output:
[{"version":"1.0"}]
[{"version":"2.0"}]
Of course that's a bit roundabout. You should follow pmf's answer. But this perhaps helps you understand why your attempt didn't go as you expected.
As a rule of thumb, try to make sure your select
and if
conditions always emit exactly one output for each input - that way you will not be surprised. Expressions that can emit zero outputs (e.g. expr?
or select(...)
) or multiple outputs (e.g. .[]
) will make for a confusing time when used as conditions in select
or if
.
CodePudding user response:
Perhaps you can use the arrays
function combined with a //
to handle the case where an input wasn't an array. e.g.,
arrays // [.]