Comparar JSON

Nada de lo que escribes aquí se almacena, registra ni comparte.

Pega dos documentos JSON para ver cada valor añadido, eliminado y modificado listado por su ruta. El orden de las claves se ignora, así que solo se reportan diferencias reales.

Cómo usarlo

  1. Pega el JSON original a la izquierda.
  2. Pega el JSON que quieres comparar a la derecha.
  3. Las diferencias aparecen debajo, cada una con su ruta completa.

Cómo funciona la comparación

Ambos documentos se analizan y se recorren en paralelo desde la raíz, comparando lo que haya en la misma ruta a cada lado. Los objetos se emparejan por clave, así que reordenar claves no cambia nada; los arrays se emparejan por índice. Cada diferencia se informa como uno de tres tipos: añadido, cuando la ruta solo existe a la derecha; eliminado, cuando solo existe a la izquierda; y modificado, cuando ambos lados tienen un valor en esa ruta pero los valores difieren.

Un cambio de tipo cuenta como modificación, y conviene mirarlo con atención cuando aparece. Un campo que era el número 42 y ahora es la cadena "42" suele seguir funcionando en un cliente tolerante y luego falla en algún punto posterior que hace cálculos con él. Lo mismo ocurre con un campo que era un objeto y ahora es un array con un solo objeto, un accidente frecuente en código que construye respuestas de forma condicional.

Por qué los arrays se comparan por índice

Es la decisión de diseño que más afecta a lo que ves, así que conviene decirlo claramente. Inserta un elemento al principio de un array de 200 elementos y una comparación por índice informará de 200 cambios, porque todos los elementos posteriores al punto de inserción están ahora en otro índice. No hay nada roto; simplemente el diff responde a una pregunta distinta de la que tenías en mente.

La alternativa es buscar la subsecuencia común más larga e informar de la inserción, que es lo que hace un diff de líneas con texto. Para JSON no es obviamente mejor. Obliga a decidir cuándo dos objetos de una lista son el mismo objeto, lo que requiere una clave que no has especificado y que la herramienta no puede adivinar, y cuando se equivoca produce una respuesta segura y engañosa. La comparación por índice es tosca, pero nunca se inventa una correspondencia que no existe.

En la práctica, cuando una lista se ha reordenado de verdad, ordena ambos lados por su identificador antes de comparar.

Null, ausencia y cómo leer una ruta

Una clave presente con valor null en un lado y ausente por completo en el otro se informa como añadida o eliminada, no como modificada, porque son estados distintos y el código los trata de forma distinta. Ausente suele significar que el campo nunca se estableció; null suele significar que se estableció a nada a propósito. Las API que usan uno con el significado del otro provocan errores, y esta es la herramienta que te muestra cuál estás enviando realmente.

Las rutas se leen como un camino desde la raíz: user.roles[2] es el tercer elemento del array roles dentro del objeto user. Esa sintaxis es deliberadamente la misma que escribirías en JavaScript para llegar al valor, y se parece lo suficiente a una expresión JSONPath como para pegarla en la mayoría de herramientas que la aceptan.

Un flujo de regresión que encuentra cambios reales

El uso más común de un diff de JSON es confirmar que un despliegue no cambió nada que no debía. Captura la respuesta de la versión actual, captúrala otra vez desde la versión candidata y compara. La dificultad nunca está en la comparación en sí, sino en el ruido, porque una respuesta casi siempre contiene campos que cambian legítimamente en cada llamada.

Elimina o normaliza esos campos antes de comparar: identificadores generados, marcas de tiempo, request ids, URLs firmadas con vencimiento y cualquier cosa que informe una duración. Un script corto que recorra el documento y reemplace esos campos por un marcador fijo convierte un diff de doscientas diferencias en un diff de la única que importa.

Haz lo mismo con el orden allí donde la API no lo garantiza. Si una lista de etiquetas vuelve en un orden distinto en cada llamada, ordena ambos lados por una clave estable antes de comparar; de lo contrario, una comparación basada en índices reporta todos los elementos como modificados y esconde entre ellos la única diferencia genuina.

Igual, idéntico y equivalente

Dos documentos pueden ser iguales en varios sentidos distintos, y una herramienta tiene que elegir uno. La igualdad byte a byte es la más estricta y la menos útil, porque un simple reformateo la rompe. La igualdad estructural - las mismas claves con los mismos valores, sin importar los espacios ni el orden de las claves - es la que realiza esta comparación. La equivalencia semántica es una tercera cosa y depende del esquema.

Los números son el punto incómodo de la comparación estructural. JSON no distingue enteros de números en coma flotante, así que 1, 1.0 y 1e0 son el mismo valor para un parser, y que un productor emita uno u otro puede cambiar entre versiones de una biblioteca sin que haya cambiado nada significativo. Los ceros finales desaparecen en un viaje de ida y vuelta por la misma razón.

Las cadenas pueden diferir en bytes y verse idénticas, porque Unicode permite más de una codificación del mismo carácter: una letra acentuada puede ser un solo punto de código o una letra base más una marca combinante. Normalizar a NFC antes de comparar elimina esa clase de diferencia falsa, y conviene hacerlo siempre que el texto llegue desde más de una fuente.

Cuándo un diff es el instrumento equivocado

Comparar dos ejemplos te dice algo sobre esos dos ejemplos. Verificar que las respuestas cumplen una regla es otro trabajo, y la herramienta para eso es un esquema: JSON Schema para un documento, OpenAPI para un endpoint. Un esquema declara que id es un entero y que email coincide con un patrón, y eso vale para todas las respuestas y no solo para el par que capturaste por casualidad.

En la práctica ambos se complementan. La validación de esquema en la integración continua detecta desviaciones estructurales - un campo que cambió de tipo, una propiedad obligatoria que desapareció - mientras que el diff responde la pregunta más acotada de qué cambió entre estos dos payloads concretos durante una investigación.

Las pruebas de contrato van un paso más allá: hacen que el consumidor publique aquello de lo que depende, de modo que el pipeline del productor falle cuando un cambio rompería a un cliente real, y no simplemente cuando difiere de una muestra vieja.

Leer una ruta y saltar hasta ella

Las rutas que se muestran aquí se leen como un camino desde la raíz: user.roles[2] es el tercer elemento del arreglo roles dentro del objeto user. Es, a propósito, la expresión que escribirías en JavaScript para llegar al valor, así que una ruta se puede pegar en una consola con el documento guardado en una variable.

La misma forma se parece a JSONPath, el lenguaje de consulta que aceptan la mayoría de las herramientas de logs y de API, donde la expresión empieza con un signo de dólar: $.user.roles[2]. jq, el procesador de línea de comandos, usa una notación similar sin el dólar, de modo que .user.roles[2] extrae el mismo valor de un archivo o de un flujo.

En documentos muy grandes, esa ruta es la salida realmente útil, más que el diff mismo. Una vez que sabes dónde vive la diferencia, jq o un filtro en tu cliente HTTP pueden traer solo ese subárbol de ambos lados, algo que se lee más rápido que cualquier vista del documento completo.

Comparar documentos demasiado grandes para leerlos

Una respuesta de varios megabytes es habitual en exportaciones analíticas y endpoints masivos, y compararla entera produce un resultado que nadie puede aprovechar. El enfoque productivo es reducir antes de comparar: selecciona el subárbol que te interesa, proyecta solo los campos en discusión y ordena las colecciones por su identificador.

Contar es una primera pasada muy útil. Si un lado tiene 4.812 registros y el otro 4.811, la pregunta interesante es qué registro falta, y eso es una comparación de conjuntos de identificadores y no un diff de documentos: dos listas ordenadas de ids y un diff por líneas lo resuelven en segundos.

Cuando los payloads son de verdad grandes y la comparación debe ejecutarse una y otra vez, una tubería de línea de comandos le gana al navegador: jq para normalizar ambos archivos en líneas compactas y ordenadas, y luego diff. En cualquiera de los dos casos todo se ejecuta localmente, pero la línea de comandos no tiene una página que mantener receptiva.

Preguntas frecuentes

¿El orden de las claves afecta a la comparación?
No. Los objetos se comparan por clave, no por posición, así que reordenar claves no produce diferencias. Los elementos de un array se comparan por índice, porque en los arrays el orden sí importa.
¿Se suben mis datos a un servidor?
No. La comparación se ejecuta íntegramente en tu navegador con JavaScript. Ninguno de los dos documentos sale de tu dispositivo, lo que lo hace seguro para respuestas de API de producción.
¿Qué significa una ruta como user.roles[2]?
Localiza la diferencia dentro del documento: el tercer elemento del array roles, dentro del objeto user. Los puntos separan claves de objeto y los corchetes indexan arrays.
¿En qué se diferencia un valor null de una clave ausente?
Se reportan de forma distinta. Una clave presente con valor null en un lado y ausente en el otro se muestra como añadida o eliminada, no como modificada.

Última actualización:

Informar de un problema

Incluye los pasos, el resultado esperado y el real. Elimina secretos y datos personales.

Herramientas relacionadas