Home > Software design >  Converting object to array or keeping an array with jq
Converting object to array or keeping an array with jq

Time:11-22

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'

Demo


Or use the deconstruction alternative operator ?// (available since jq 1.6):

jq '. as [$v] ?// $v | [$v]' 

Demo

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 // [.]

Demo

  • Related