es.davy.ai

Preguntas y respuestas de programación confiables

¿Tienes una pregunta?

Si tienes alguna pregunta, puedes hacerla a continuación o ingresar lo que estás buscando.

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!

Tags:  , , ,

Answer

  1. Avatar for 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:

    (($X|fromjson)|setpath(($Z|fromjson)|paths as $p | [$p, (($Z|fromjson)|getpath($p))])|tojson)-$X
    

    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.

Comments are closed.