Elegir una herramienta de diff de JSON: diff de texto, diff estructural y visualización
En resumen: El diff de texto encuentra dónde difieren los archivos; el diff estructural decide si los contenidos son equivalentes. Elegir la capa correcta evita que el orden de las claves se confunda con un incidente.

En resumen: el diff de texto descubre el cambio (ordena primero las claves con jq -S), el diff estructural decide la equivalencia (alinea los arrays por clave primaria) y la visualización solo lo explica o lo revisa; mantén las tres capas separadas en CI.
Que dos documentos JSON se vean diferentes no significa que sus contenidos sean distintos. Elige la capa de comparación correcta antes de elegir la herramienta.
1. Tres tipos de "diff" no son lo mismo
Muchos equipos tratan "comparar JSON" como una sola acción y luego se topan con los mismos problemas en escenarios distintos. Ayuda separar tres capas:
| Capa | Qué se compara | Qué revela | Uso típico |
|---|---|---|---|
| Diff de texto | Bytes y líneas sin procesar | Indentación, orden de claves, espacios, saltos de línea | Deriva de configuración, revisión de código |
| Diff estructural | Objetos y arrays ya parseados | Claves añadidas o eliminadas, valores cambiados, cambios en elementos de array | Regresión de API, migración de datos |
| Diff visual | Cómo se presentan las diferencias | A qué nivel pertenece un cambio, cuán grande es su radio de impacto | Revisión humana, discusión en equipo |
El diff de texto no tiene dependencias, es rápido y se integra de forma natural con Git. Su debilidad es que no sabe nada de la semántica de JSON. Dos documentos completamente equivalentes se iluminarán en rojo y verde si difieren su orden de claves o su indentación. El diff estructural es lo contrario: primero parsea y luego compara, así que reconoce que un valor simplemente se movió. Pero necesita la estrategia correcta de alineación de arrays; de lo contrario, una sola inserción se malinterpreta como una reescritura completa.
La visualización no es un tercer algoritmo. Representa la salida de las otras dos capas para que una persona pueda confirmar rápidamente si la diferencia es esperada. Sirve para el juicio humano y es mala como sustituto de las comprobaciones automatizadas. Mezclar las capas suele producir un pipeline de CI permanentemente en rojo y un equipo que ha aprendido a ignorarlo.
2. Diff de texto: primero ordena las claves con jq -S
Para echar un vistazo rápido a dos archivos, la combinación más barata es jq -S más diff -u. La opción -S ordena recursivamente las claves de los objetos, normalizando documentos cuyo contenido es idéntico pero cuyo orden de claves difiere:
jq -S . a.json > a.sorted.json
jq -S . b.json > b.sorted.json
diff -u a.sorted.json b.sorted.json
Esto elimina el falso positivo más común. RFC 8259 define los objetos como colecciones no ordenadas, así que {"a":1,"b":2} y {"b":2,"a":1} son el mismo valor. Un diff de texto, en cambio, marca cada línea como cambiada. Ordenar primero acerca mucho los bloques rojos a los problemas reales.
Ten en cuenta que jq -S solo ordena las claves; no ordena los arrays. El orden de un array tiene significado en JSON, y [1,2] no es el mismo valor que [2,1], así que la herramienta no tomará esa decisión por ti. Si un array es conceptualmente un conjunto no ordenado, ordénalo tú mismo por una clave primaria antes de comparar.
Hay otro detalle que conviene recordar: diff sale con código 1 cuando encuentra diferencias, pero un archivo inexistente o un error de permisos también produce una salida distinta de cero. En CI, no trates cualquier código distinto de cero como "el contenido cambió", o una ruta mal escrita se reportará como un cambio de datos. Un patrón más seguro verifica primero los archivos y luego gestiona la diferencia:
if ! diff -u a.sorted.json b.sorted.json; then
echo "JSON content differs" >&2
exit 1
fi
delta hace legible el diff de texto
La salida cruda de diff -u funciona, pero las líneas largas y el JSON profundamente anidado son cansados de leer. delta es un filtro de diff que añade resaltado de sintaxis, números de línea y colores de fondo más claros:
diff -u a.sorted.json b.sorted.json | delta
Nació como paginador de Git, así que también puede encargarse de git diff. Para quienes leen diez diffs de configuración al día, herramientas así mejoran la velocidad de lectura más que la precisión del algoritmo. Otra opción habitual es git diff --no-index --word-diff, que compara a nivel de palabra y funciona bien con JSON compactado guardado en líneas muy largas.
Una advertencia: embellecer no elimina las falsas diferencias. Si el orden de las claves no se ha normalizado, delta simplemente hace más bonito el ruido. El orden correcto es siempre normalizar primero, decorar después.
3. Diff estructural: jsondiffpatch entiende la semántica
Cuando la pregunta es si dos piezas de datos son semánticamente equivalentes, usa un diff estructural. La opción más común en el ecosistema JavaScript es jsondiffpatch, que devuelve un objeto delta que describe los cambios en lugar de líneas de texto con marcas + y -:
import { create } from 'jsondiffpatch';
const instance = create();
const delta = instance.diff(
{ id: 1, tags: ['a', 'b'], enabled: true },
{ id: 1, tags: ['a', 'c'], enabled: false },
);
console.log(JSON.stringify(delta, null, 2));
El delta distingue claves añadidas, claves eliminadas, valores reemplazados y elementos de array movidos. Eso significa que puedes recorrer el delta directamente en las aserciones: undefined cuando no hay diferencia, o una descripción ruta por ruta cuando la hay. A diferencia de un diff de texto, no le afectan la indentación, el orden de las claves ni la forma de escribir los números, como 1e3 frente a 1000.
Igual de importante: la salida del diff estructural puede alimentar un flujo de aplicación de parches. jsondiffpatch ofrece patch y reverse, así que un delta se puede aplicar al objeto original y deshacer después. Una advertencia: un delta generado automáticamente no tiene el mismo formato que un JSON Patch escrito a mano, y convertir entre ambos requiere trabajo. Para el enfoque de comparación más amplio, consulta Diff y comparación de JSON.
4. Cómo eliminar las falsas diferencias causadas por el orden de las claves
El orden de las claves no solo molesta; puede llevar a conclusiones erróneas. Sigue tres pasos al investigar:
- Antes de comparar, normaliza recursivamente el orden de las claves con
jq -So una herramienta equivalente. - Elimina los campos que cambian en cada ejecución, como
timestamp,requestIdytraceId. - Confirma que no hay claves duplicadas. Las claves duplicadas se resuelven con "gana la última" durante el parseo, así que un diff de texto las ve mientras que un diff estructural no.
jq -S 'del(.meta.timestamp, .meta.requestId)' a.json > /tmp/a.json
jq -S 'del(.meta.timestamp, .meta.requestId)' b.json > /tmp/b.json
diff -u /tmp/a.json /tmp/b.json
Este flujo encaja bien con la deriva de configuración. Pasa staging y producción por la misma normalización y lo que quede será la diferencia que realmente necesita explicación. Mantén las reglas de filtrado en un script versionado en lugar de reescribirlas de memoria. Para la parte de edición y scripting de la cadena de herramientas, consulta Editores, línea de comandos y automatización.
5. Arrays: ¿alinear por índice o por id?
Los arrays son la parte más propensa a errores de la comparación de JSON. Con la alineación por índice predeterminada, insertar un registro al principio desplaza todos los elementos posteriores y la herramienta los marca a todos como cambiados. La herramienta no está rota; la estrategia de alineación es incorrecta.
| Alineación | Funciona cuando | Fortaleza | Riesgo |
|---|---|---|---|
| Por índice | El orden del array tiene significado de negocio | Simple, no necesita campos extra | Una inserción provoca falsos positivos en cascada |
| Por clave primaria | Los elementos son un conjunto con un id único | Detecta inserción, eliminación y movimientos | Requiere una clave estable y única |
| Subsecuencia común más larga | El orden importa pero se permiten inserciones | Más robusta que la indexación pura | Más difícil de explicar, mayor coste |
jsondiffpatch usa objectHash para declarar una clave primaria, indexando los elementos del array antes de comparar:
const instance = create({
objectHash: (item: { id?: string; sku?: string }) => item.id ?? item.sku,
});
Con jq simple, puedes ordenar primero y luego usar un diff de texto:
jq -S 'sort_by(.id)' items.json > items.sorted.json
El compromiso de la alineación por id es que los cambios de posición dejan de contar como diferencias. Si el negocio necesita saber que un registro pasó de la posición 2 a la 5, compara también un campo de orden explícito; no te fíes solo del contenido del conjunto.
6. Diff visual: la capa construida para personas
La salida del diff estructural está diseñada para máquinas, no para reuniones. Cuando necesitas explicar un cambio a otra persona, la visualización es más eficaz:
- El editor de diff de VS Code es bueno para revisar archivo por archivo, con paneles lado a lado y resaltados en línea.
- Los visores de árbol como JSON Crack y JSON Hero muestran a qué nivel pertenece un cambio.
jsondiffpatchincluye un formateador HTML que representa un delta como una vista enriquecida plegable.
El valor de la visualización es el contexto. Un diff de texto dice que la línea 88 cambió; una vista de árbol dice que la línea 88 pertenece a data.items[3].price. Pero la visualización normalmente carga el documento completo en un navegador o en una aplicación de escritorio. Antes de abrir un payload de producción, confirma si la herramienta calcula localmente o sube los datos a un servidor.
7. Tres prácticas para incorporar el diff en CI
Primero, normaliza antes de comparar. Fija la indentación, ordena las claves recursivamente y filtra los campos volátiles antes de que se ejecute el diff. De lo contrario, CI fallará una y otra vez por ruido.
Segundo, elige la alineación según la semántica. Si un array es un conjunto, alinea por clave primaria. Si es una secuencia, mantén la alineación por índice. No apliques una sola regla a todas las API.
Tercero, afirma valores, no texto. Comparar objetos parseados en las pruebas es mucho más estable que comparar la salida de JSON.stringify. Cuando una comprobación falla, imprime la ruta de la diferencia y los valores antes y después en lugar de volcar todo el payload en el log.
# Imprime solo las rutas y los valores que difieren, no el documento completo
jq -S 'del(.timestamp)' actual.json > /tmp/actual.json
jq -S 'del(.timestamp)' expected.json > /tmp/expected.json
diff -u /tmp/expected.json /tmp/actual.json | head -n 80
Si una diferencia necesita seguimiento a largo plazo, haz commit del script de normalización y del archivo esperado juntos. Si es una investigación puntual, borra el script temporal después para que no se convierta en silencio en una segunda implementación de la lógica de negocio.
8. Tabla de selección de herramientas
| Escenario | Primera opción | Alternativa | Recordatorio clave |
|---|---|---|---|
| Comparación ocasional de archivos | Diff de VS Code | diff -u |
Formatea primero para eliminar ruido |
| Deriva de configuración | jq -S + diff |
delta |
Filtra los campos volátiles |
| Regresión de respuestas de API | jsondiffpatch |
deepdiff |
Alinea los arrays por clave primaria |
| Explicar un cambio al equipo | JSON Crack / JSON Hero | Formateador HTML | Comprueba si los datos se suben |
| Archivos muy grandes | Extracción de subárbol con jq |
Navegación con jless |
No cargues el árbol completo en memoria |
| Comprobación previa al commit | git diff --no-index |
pre-commit | Gestiona los códigos de salida explícitamente |
No existe una única mejor herramienta de diff de JSON; solo existe la herramienta correcta para la capa actual. El diff de texto descubre el cambio, el diff estructural juzga la equivalencia y la visualización lo explica. Mantener las tres capas separadas es mucho más fiable que buscar una herramienta que lo haga todo. Para el manejo de conflictos entre ramas, continúa con JSON y fusión en Git.
FAQ
¿jq -S también cambia el orden de los arrays?
No. -S solo ordena las claves de los objetos y deja los arrays en su orden original. Los arrays están ordenados en JSON, así que la herramienta no convertirá [1,2] en [2,1]. Si un array es conceptualmente un conjunto no ordenado, ordénalo explícitamente con sort_by.
¿1 y 1.0 son el mismo valor en un diff estructural?
La mayoría de los parsers convierten ambos en el mismo número, así que un diff estructural normalmente los considera iguales, mientras que un diff de texto los marca como diferentes. Usa el diff estructural para decidir la equivalencia y el diff de texto para decidir si los archivos son idénticos byte a byte.
¿Se puede usar directamente la salida de diff como JSON Patch?
No directamente. Una herramienta de diff informa de lo que difiere, mientras que JSON Patch describe una lista ordenada de operaciones; los formatos y la semántica son distintos. Los parches autogenerados además suelen referenciar índices de array, que quedan obsoletos con concurrencia. Añade una operación test antes de aplicarlos.
¿Cómo comparar archivos JSON de decenas de megabytes?
Extrae primero el subárbol que te interesa, por ejemplo con jq '.data.items', luego ordena por clave primaria y compara. No cargues el árbol completo en memoria para un diff recursivo. Los archivos muy grandes se manejan mejor con comparación por streaming o por bloques, y el coste de memoria requiere una evaluación aparte.
¿Pueden filtrar mis datos las herramientas de diff en línea?
Depende de la implementación. Las herramientas puramente de front-end calculan en el navegador y no suben datos; las herramientas renderizadas en servidor envían el contenido a un backend, donde puede acabar en los logs. Antes de comparar configuración de producción o payloads de clientes, confirma que la página calcula localmente y pruébalo una vez con la red desconectada si hace falta.
¿Cómo decido entre un diff de texto y un diff estructural?
Para un vistazo rápido a dónde difieren dos archivos, ordena las claves con jq -S y ejecuta diff -u. Para decidir si dos documentos son semánticamente equivalentes, usa un diff estructural como jsondiffpatch y alinea los arrays por clave primaria. El diff de texto no tiene dependencias y se integra con Git, mientras que al diff estructural no le afectan la indentación, el orden de las claves ni la forma de escribir los números.
¿Por qué CI no debe tratar cualquier código de salida distinto de cero de diff como una diferencia?
diff sale con 1 cuando encuentra diferencias, pero un archivo inexistente o un error de permisos también devuelve un valor distinto de cero. Tratar cualquier código distinto de cero como “contenido cambiado” reporta una ruta mal escrita como un cambio de datos. Un patrón más seguro verifica primero los archivos y luego gestiona la diferencia por separado.
¿Debo comparar con diff archivos de decenas de megabytes en el navegador?
No. Extrae primero el subárbol que te interesa con jq, luego ordena por clave primaria y compara. Cargar el árbol completo en memoria para un diff recursivo cuesta mucho más memoria y tiempo. Los archivos muy grandes se manejan mejor con comparación por streaming o por bloques.