jq: cómo realizar la desunión / sustracción de objetos de varios niveles, es decir, dado X e Y, encontrar Z tal que X * Z = Y
Usando jq
podemos fusionar fácilmente dos objetos multinivel X e Y utilizando *
:
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'"
nos da:
Z='{
"a": 3,
"b": 5,
"c": {
"a": 3,
"x": 10,
"y": 11
},
"d": 2
}'
Pero en mi caso, estoy comenzando con X y Z y quiero calcular Y (tal que X * Y = Z). Si solo tenemos objetos con propiedades escalares, entonces jq X + Y
es igual a Z
, y también podemos calcular Y
como jq Z - X
. Sin embargo, esto falla si X o Y contienen propiedades con valores de objeto como en el ejemplo anterior:
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]'
arroja un error jq: error (at <stdin>:16): object ({"a":3,"b":...) and object ({"a":1,"b":...) cannot be subtracted
¿Existe una solución elegante a este problema con jq?
ACTUALIZACIÓN: He aceptado la respuesta que encontré más fácil de leer/mantener y con un rendimiento superior. Además, encontré un detalle en mi necesidad que era que si X contenía una clave K que no estaba presente en Z, necesitaba que la salida (Y) la anulara al contener la clave K con un valor nulo.
La mejor manera que se me ocurrió para hacer esto fue preprocesar Z para agregar las claves faltantes usando lo siguiente:
def add_null($y):
reduce (to_entries[] | [ .key, .value ] ) as [ $k, $v ] (
$y;
if $y | has($k) | not then
.[$k] = null
elif $v | type == "object" then
.[$k] = ($v | add_null($y[$k]))
else
.[$k] = $v
end
);
así obtenemos:
def add_null(...);
def remove(...);
. as [ $X, $Z ] | ($X | add_null($Z)) | remove($X)
¡Se agradecen sugerencias mejores para esta variante!
davy.ai
El problema al intentar calcular Y como
jq Z - X
cuando X o Y contienen propiedades con valores de objeto es que los objetos no se pueden restar en jq.Una posible solución es utilizar la función
reduce
para fusionar recursivamente los objetos en X y Y, comparando cada nivel y reparando cualquier clave faltante en el proceso antes de calcular Y.Otra solución es utilizar
setpath
para fusionar X e Y y luego resta X del resultado para obtener Y. Esto se puede hacer en una línea de la siguiente manera:Aquí,
($X|fromjson)
convierte la representación de cadena de X en un objeto JSON, y($Z|fromjson)
convierte la representación de cadena de Z en un objeto JSON.paths as $p
genera todos los caminos en el objeto Z, y[$p, (($Z|fromjson)|getpath($p))]
empareja cada camino en Z con su valor correspondiente. Luego,setpath
establece cada par de camino-valor en el objeto XY. Finalmente,tojson
devuelve la representación JSON del objeto XY, y se resta el resultado de X para obtener Y.