Using jq we can easily merge two multi-level objects X and Y using *:
X='{
"a": 1,
"b": 5,
"c": {
"a": 3
}
}' Y='{
"d": 2,
"a": 3,
"c": {
"x": 10,
"y": 11
}
}' && Z=`echo "[$X,$Y]"|jq '.[0] * .[1]'` && echo "Z='$Z'"
gives us:
Z='{
"a": 3,
"b": 5,
"c": {
"a": 3,
"x": 10,
"y": 11
},
"d": 2
}'
But in my case, I'm starting with X and Z and want to calculate Y (such that X * Y = Z). If we only have objects with scalar properties, then jq X Y equals Z, and we can also calculate Y as jq Z - X. However, this fails if X or Y contain properties with object values such as in the above example:
X='{
"a": 1,
"b": 5,
"c": {
"a": 3
}
}' Z='{
"a": 3,
"b": 5,
"c": {
"a": 3,
"x": 10,
"y": 11
},
"d": 2
}' && echo "[$X,$Z]" | jq '.[1] - .[0]'
throws an error jq: error (at <stdin>:16): object ({"a":3,"b":...) and object ({"a":1,"b":...) cannot be subtracted
Is there an elegant solution to this problem with jq?
CodePudding user response:
I don't know if this is elegant, but it works for your sample data
echo "[$X,$Z]" | jq '
. as [$x,$z]
| map([paths(scalars)])
| .[0] |= map(select(. as $p | [$x, $z | getpath($p)] | .[1] == .[0]))
| reduce (.[1] - .[0])[] as $p ({}; setpath($p; $z | getpath($p)))
'
{
"a": 3,
"c": {
"x": 10,
"y": 11
},
"d": 2
}
CodePudding user response:
def remove($o2):
reduce ( to_entries[] | [ .key, .value ] ) as [ $k, $v1 ] (
{};
if $o2 | has($k) | not then
# Keep existing value if $o2 doesn't have the key.
.[$k] = $v1
else
$o2[$k] as $v2 |
if $v1 | type == "object" then
# We're comparing objects.
( $v1 | remove($o2[$k]) ) as $v_diff |
if $v_diff | length == 0 then
# Discard identical values.
.
else
# Keep the differences of the values.
.[$k] = $v_diff
end
else
# We're comparing non-objects.
if $v1 == $v2 then
# Discard identical values.
.
else
# Keep existing value if different.
.[$k] = $v1
end
end
end
);
. as [ $Z, $X ] | $Z | remove($X)
Demo on jqplay
or
def sub($v2):
( type ) as $t1 |
( $v2 | type ) as $t2 |
if $t1 == $t2 then
if $t1 == "object" then
with_entries(
.key as $k |
.value = (
.value |
if $v2 | has($k) then sub( $v2[$k] ) else . end
)
) |
select( length != 0 )
else
select( . != $v2 )
end
else
.
end;
. as [ $Z, $X ] | $Z | sub($X)
Demo on jqplay
