Comparar dos estructuras JSON

compara dos estructuras JSON y lista las adiciones, eliminaciones y modificaciones, clave por clave

¿Por qué comparar dos JSON?

Comparar dos estructuras JSON aparece con regularidad en la vida de un desarrollador. Una respuesta de API que cambia tras una actualización. Un fichero de configuración que diverge entre dos entornos. Una exportación de objetos que debe alinearse con una referencia. Un diff JSON responde precisamente a esa pregunta: ¿qué se ha añadido, suprimido o modificado, y en qué punto?

Un diff línea a línea (a la git diff) a menudo no basta. Si el formateo difiere (espacios, orden de las claves), el diff textual señala cientos de líneas mientras la estructura de datos es idéntica. Un comparador JSON trabaja sobre la estructura una vez parseada, lo que elimina ese ruido y revela únicamente las desviaciones semánticas.

El formato del diff producido por la herramienta

Para cada desviación, la herramienta devuelve:

  • una ruta en formato JSONPath simplificado, por ejemplo $.user.address[0].city;
  • un tipo entre added (añadido a la derecha), removed (presente a la izquierda solamente), modified (valores distintos);
  • el valor de la izquierda y/o el valor de la derecha según el tipo.

Un diff vacío significa que los dos JSON son estructuralmente idénticos, independientemente de su formateo o del orden de las claves.

Cómo recorre el algoritmo las dos estructuras

El algoritmo es recursivo. En cada nivel, identifica el tipo de los dos valores comparados:

  • Si los dos son objetos asociativos, toma la unión de las claves. Para cada clave, desciende recursivamente, o marca added/removed si la clave solo existe en un lado.
  • Si los dos son tablas ordenadas, compara posición por posición. Una diferencia al principio de la tabla puede desplazar todo el resto, lo que produce un diff verboso: es un límite asumido del diff estructural ingenuo.
  • Si los tipos difieren (objeto frente a tabla, escalar frente a null), se marca modified.
  • Si los dos valores son escalares (cadena, número, booleano, null), una simple comparación estricta basta.

El orden de las claves en un objeto no cuenta: {"a": 1, "b": 2} y {"b": 2, "a": 1} producen un diff vacío. Esto se ajusta a la semántica JSON, donde el orden no es significativo. En cambio, el orden de los elementos de una tabla sí cuenta: una tabla está ordenada por construcción.

Un ejemplo concreto

Estas son dos versiones de un objeto de usuario:

// gauche
{
  "id": 42,
  "name": "Alice",
  "roles": ["admin", "editor"]
}

// droite
{
  "id": 42,
  "name": "Alice Martin",
  "roles": ["admin", "viewer"],
  "active": true
}

El diff produce:

  • $.name: modified, "Alice""Alice Martin"
  • $.roles[1]: modified, "editor""viewer"
  • $.active: added, true

Casos de uso

  • Diff de entornos: comparar la salida de un endpoint en preproducción y en producción. Muy útil durante una migración o una actualización de caché.
  • Auditoría de migración: comparar una exportación antes y después de la transformación para comprobar que no se ha perdido ningún campo.
  • Regresión de API: antes y después de una modificación, comparar la respuesta para una misma petición. Un diff vacío confirma la ausencia de regresión.
  • Sincronización de configuraciones: comparar composer.json entre dos ramas, dos ficheros .eslintrc, dos configuraciones de Symfony.
  • Snapshot tests: sustituir una comparación línea a línea por una comparación estructural en una suite de tests de integración.

Límites del diff estructural

Comparar tablas ordenadas es un límite conocido de los diffs estructurales ingenuos. Si se inserta un elemento al principio de la tabla, todas las posiciones siguientes se desplazan y el diff señala cada desviación como una modificación. Para esos casos, existen algoritmos más avanzados (Myers, Patience, diff por clave natural), pero quedan fuera del alcance de una herramienta de comparación generalista.

El diff tampoco dice por qué ha ocurrido un cambio: es una constatación. Para analizar una regresión, hay que cruzar esa constatación con los commits, los despliegues y los parámetros de la petición.

JSON diff vs JSON Patch (RFC 6902)

Un formato complementario es JSON Patch (RFC 6902). Describe, en forma de operaciones (add, remove, replace, move, copy, test), cómo transformar un documento JSON en otro. Allí donde nuestro diff es una constatación (humana), JSON Patch es una receta (máquina). Las dos representaciones son equivalentes para los casos sencillos, y JSON Patch resulta útil para API RESTful que aceptan modificaciones parciales.

Preguntas frecuentes

¿El diff depende del orden de las claves?

No: para un objeto JSON, el orden no es significativo. El comparador produce el mismo resultado tanto si las claves están ordenadas como si no.

¿Cómo gestionar las tablas cuyo orden no es importante?

La herramienta considera por defecto las tablas como ordenadas (es la semántica JSON). Si trata conjuntos, ordene las dos tablas por una clave natural antes de comparar, o utilice un servicio de comparación especializado que tenga en cuenta esa semántica.

¿Cuál es la diferencia con un diff Git?

Git compara líneas de texto. Si la indentación o el orden de las claves difiere, el diff Git es muy verboso. El diff JSON trabaja sobre la estructura parseada y solo señala las desviaciones de datos.

¿Se acepta un JSON no válido?

No: si alguno de los dos JSON no parsea, la herramienta devuelve un error. Valide primero con nuestro validador JSON.

Ejemplo de solicitud

curl -X POST https://cdrn.fr/api/v1/tools/json-diff/execute \
  -H "Content-Type: application/json" \
  -d '{"left":"...","right":"..."}'

Esquema de entrada

Campo Tipo Obligatorio Por defecto
left text
right text

Puntos de acceso

  • GET https://cdrn.fr/api/v1/tools - lista todas las herramientas disponibles
  • GET https://cdrn.fr/api/v1/tools/json-diff - recupera el esquema de esta herramienta
  • POST https://cdrn.fr/api/v1/tools/json-diff/execute - ejecuta esta herramienta con un payload JSON