jq: join string array whether it is empty or not - json

I have a JSON test.json as follows:
[
{
"a": "a",
"b": [
"a",
"b",
"c"
]
},
{
"a": "a"
}
]
And I would want to join the field b of each entry and handle the case of its
emptiness:
{ "a": "a",
"b": "a, b, c"
},
{
"a": "a",
"b": null
}
The following command works...
cat test.json |
jq '.[] | .b as $t | if $t then {a: .a, b: $t | join(", ")} else {a: .a, b: $t} end'
... but it's too long as I have to write almost the same constructor two times.
I have tried to move the if-then-else conditional or even the // operator in the {} construction, but they result to a syntax error.

Depending on how you want to handle null/empty values you could try these:
map(.b |= (. // [] | join(", ")))
map(if .b then .b |= join(", ") else . end)

Related

Modify arrays within objects in jq

I have an array of objects, and want to filter the arrays in the b property to only have elements matching the a property of the object.
[
{
"a": 3,
"b": [
1,
2,
3
]
},
{
"a": 5,
"b": [
3,
5,
4,
3,
5
]
}
]
produces
[
{
"a": 3,
"b": [
3
]
},
{
"a": 5,
"b": [
5,
5
]
}
]
Currently, I've arrived at
[.[] | (.a as $a | .b |= [.[] | select(. == $a)])]
That works, but I'm wondering if there's a better (shorter, more readable) way.
I can think of two ways to do this with less code and both are variants of what you have already figured out on your own.
map(.a as $a | .b |= map(select(. == $a)))
del(.[] | .a as $a | .b[] | select(. != $a))

JQ - Denormalize nested object

I've been trying to convert some JSON to csv and I have the following problem:
I have the following input json:
{"id": 100, "a": [{"t" : 1,"c" : 2 }, {"t": 2, "c" : 3 }] }
{"id": 200, "a": [{"t": 2, "c" : 3 }] }
{"id": 300, "a": [{"t": 1, "c" : 3 }] }
And I expect the following CSV output:
id,t1,t2
100,2,3
200,,3
300,3,
Unfortunately JQ doesn't output if one of select has no match.
Example:
echo '{ "id": 100, "a": [{"t" : 1,"c" : 2 }, {"t": 2, "c" : 3 }] }' | jq '{t1: (.a[] | select(.t==1)).c , t2: (.a[] | select(.t==2)).c }'
output:
{ "t1": 2, "t2": 3 }
but if one of the objects select returns no match it doesn't return at all.
Example:
echo '{ "id": 100, "a": [{"t" : 1,"c" : 2 }] }' | jq '{t1: (.a[] | select(.t==1)).c , t2: (.a[] | select(.t==2)).c }'
Expected output:
{ "t1": 2, "t2": null }
Does anyone know how to achieve this with JQ?
EDIT:
Based on a comment made by #peak I found the solution that I was looking for.
jq -r '["id","t1","t2"],[.id, (.a[] | select(.t==1)).c//null, (.a[] | select(.t==2)).c//null ]|#csv'
The alternative operator does exactly what I was looking for.
Alternative Operator
Here's a simple solution that does not assume anything about the ordering of the items in the .a array, and easily generalizes to arbitrarily many .t values:
# Convert an array of {t, c} to a dictionary:
def tod: map({(.t|tostring): .c}) | add;
["id", "t1", "t2"], # header
(inputs
| (.a | tod) as $dict
| [.id, (range(1;3) as $i | $dict[$i|tostring]) ])
| #csv
Command-line options
Use the -n option (because inputs is being used), and the -r option (to produce CSV).
This is an absolute mess, but it works:
$ cat tmp.json
{"id": 100, "a": [{"t" : 1,"c" : 2 }, {"t": 2, "c" : 3 }] }
{"id": 200, "a": [{"t": 2, "c" : 3 }] }
{"id": 300, "a": [{"t": 1, "c" : 3 }] }
$ cat filter.jq
def t(id):
.a |
map({key: "t\(.t)", value: .c}) |
({t1:null, t2:null, id:id} | to_entries) + . | from_entries
;
inputs |
map(.id as $id | t($id)) |
(.[0] | keys) as $hdr |
([$hdr] + map(to_entries |map(.value)))[]|
#csv
$ jq -rn --slurp -f filter.jq tmp.json
"id","t1","t2"
2,3,100
,3,200
3,,300
In short, you produce a direct object containing the values from your input, then add it to a "default" object to fill in the missing keys.

jq JSON handle missing fields

Normally, I want to do something like this, which works:
echo '{"a": 123, "b": "{\"embedded\": 456}", "c": 789}' | jq '{a, "b2": .b | fromjson, c}'
that works and yields:
{
"a": 123,
"b2": {
"embedded": 456
},
"c": 789
}
Sometimes, field "b" is missing and in that case the above jq will error:
echo '{"a": 123, "c": 789}' | jq '{a, "b2": .b | fromjson, c}'
That gives a null error, which makes sense because b is missing so .b is null and fromjson errors on null. I want to write my jq command so if b is missing for a record, then it isn't output. I want to simply get this:
{
"a": 123,
"c": 789
}
I've skimmed through the jq Reference manual. I'm having trouble getting the if/else/end construct to solve this. I don't see any other null handling functions or language constructs available.
There are several possible approaches, each with variants. Here are illustrations of a constructive approach, and a postprocessing one.
Constructive
{a} + ((select(has("b")) | {"b2": .b | fromjson}) // {}) + {c}
or more concisely but with slightly different semantics:
[{a}, (select(.b) | {b2: .b | fromjson}), {c}] | add
Postprocessing
{a, "b2": (.b | fromjson? // null), c}
| if .b2 == null then del(.b2) else . end
You need something like below. Use the if condition to check against the presence of .b to be null. Form the structure to have all fields when not null.
jq 'if .b != null then { a, "b2":(.b|fromjson), c } else {a, c} end'
This works the best, combining suggestions from peak and Inian's answers:
# example with b
echo '{"a": 123, "b": "{\"embedded\": 456}", "c": 789}' | jq '{a, c} + if has("b") then {"b": .b | fromjson} else {} end'
# example without b
echo '{"a": 123, "not_b": "{\"embedded\": 456}", "c": 789}' | jq '{a, c} + if has("b") then {"b": .b | fromjson} else {} end'

How can a sub-object be pruned from a json object using jq?

In the following, I am trying to delete one of the two objects in the "bar" array, the one where "v" == 2:
{
"foo": {},
"bar": [
{
"v": 2
},
{
"v": 1
}
]
}
I am able to first only keep the list, then delete the matching object:
.bar[] | select(.v ==2 | not)
returns:
{
"v": 1
}
Is there a way to delete a sub-object to keep the enclosing object:
{
"foo": {},
"bar": [
{
"v": 1
}
]
}
Along the lines of the given attempt, namely:
.bar[] | select(.v ==2 | not)
you would use the |= operator, e.g.:
.bar |= map(select(.v ==2 | not))
Or simply:
.bar |= map(select(.v != 2))
If you only wanted to delete the first match, you could write:
.bar |= (index({v:2}) as $i| .[:$i] + .[$i+1:])
or more robustly:
.bar |= (index({v:2}) as $i
| if $i then .[:$i] + .[$i+1:] else . end)
or if you prefer:
.bar |= ( ([.[].v]|index(2)) as $i
| if $i then del(.[$i]) else . end)
Use the del operator to delete the node you want:
<file jq 'del(.bar[] | select(.v==2))'

Swap keys in nested objects using JQ

Using jq, how can I transform:
{ "a": {"b": 0}, "c": {"d": 1}}
into:
{"b": {"a": 0}, "d": {"c": 1}}
without knowing the name of the keys in the source?
(I know that this can lose data in the general case, but not with my data)
Here's an alternative using with_entries:
with_entries(.key as $parent
| (.value|keys[0]) as $child
| {
key: $child,
value: { ($parent): .value[$child] }
}
)
def swapper:
. as $in
| reduce keys[] as $key
( {}; . + ( $in[$key] as $o
| ($o|keys[0]) as $innerkey
| { ($innerkey): { ($key): $o[$innerkey] } } ) ) ;
Example:
{ "a": {"b": 0}, "c": {"d": 1}} | swapper
produces:
{"b":{"a":0},"d":{"c":1}}
Here is a solution which uses jq streams and variables:
[
. as $d
| keys[]
| $d[.] as $v
| ($v|keys[]) as $vkeys
| {
($vkeys): {
(.): ($vkeys|$v[.])
}
}
] | add
It is easy to lose track of what is what at the end so to see more clearly what's going on here is a slightly expanded version with additional comments and variables.
[
. as $d # $d: {"a":{"b":0},"c":{"d": 1}}
| keys[] | . as $k # $k: "a", "c"
| $d[$k] as $v # $v: {"b": 0}, {"d": 1}
| ($v|keys[]) as $vkeys # $vkeys: "b", "d"
| ($vkeys|$v[.]) as $vv # $vv: 0, 1
| {
($vkeys): { # "b": { "d": {
($k): $vv # "a": 0 "c": 1
} # } , }
}
] | add