API y empresas

Claves JSON duplicadas: dos clientes leen distinta la misma multa

Dos campos `status` o `amount` pueden atravesar validación sintáctica y terminar con valores distintos según el parser. La frontera segura los rechaza antes del binding.

Por Marco Ferreiro ·

Dos fichas duplicadas que producen salidas distintas en bandejas mecánicas

El cuerpo {"status":"complete","status":"partial"} parece pequeño, pero contiene dos decisiones incompatibles. Un cliente puede ver partial; otro puede rechazar el mensaje. Si la clave duplicada fuera amount, la diferencia cambiaría el importe de un acta en una conciliación automática.

La respuesta de una consulta de infracciones no se vuelve segura porque una herramienta diga “JSON válido”. Validez gramatical e interoperabilidad son controles distintos.

Qué establece RFC 8259

RFC 8259 define un objeto como una secuencia de pares nombre y valor. Su gramática no prohíbe repetir el nombre, pero indica que los nombres deberían ser únicos.

La norma explica por qué: con nombres únicos, las implementaciones acuerdan el mapeo. Con duplicados, el comportamiento es impredecible; muchas conservan el último par, otras fallan y algunas exponen todas las apariciones.

Por eso describir la respuesta de actas como “válida” sin aclaración resulta incompleto. Puede ser aceptado por la gramática y no ser interoperable.

El dato se pierde al convertirlo en mapa

La mayoría de las aplicaciones transforma el objeto en un diccionario o mapa. Esa estructura admite una sola entrada por clave. Cuando llega la segunda, la primera se reemplaza o provoca error.

Si la validación del schema ocurre después, ya no puede saber que existieron dos campos. Ve un objeto aparentemente normal con un único status para el acta.

La detección debe suceder mientras el parser recorre los tokens y todavía conserva cada aparición del nombre.

El fixture mínimo

El laboratorio usa exactamente los mismos bytes en cuatro ecosistemas:

{"status":"complete","amount":0,"status":"partial","amount":12500}

No representa una multa real. Los valores son marcadores sintéticos elegidos para que cualquier diferencia sea visible.

También probamos duplicados dentro del objeto anidado de una jurisdicción, nombres que sólo cambian mayúsculas y claves Unicode equivalentes visualmente pero con codificación distinta. Estas últimas no son duplicados de bytes y necesitan otra regla de normalización.

JavaScript y Python conservan el último

La especificación ECMAScript dice expresamente que, cuando hay nombres duplicados, los valores anteriores para la misma clave se sobrescriben. JSON.parse termina con status = partial y amount = 12500.

La documentación de Python informa el mismo comportamiento predeterminado: acepta nombres repetidos y usa el valor del último par. También ofrece object_pairs_hook, que permite recibir la secuencia y detectar repeticiones antes de construir el diccionario definitivo.

La coincidencia entre estos dos clientes no crea una regla universal. Sólo demuestra una configuración concreta.

Java: la opción debe ser explícita

Java no trae un único parser JSON obligatorio. En el fixture con Jackson, registramos versión y configuración. La detección estricta de duplicados hace que el parser arroje error; sin ella, no asumimos que el contrato está protegido.

La prueba de integración exige activar STRICT_DUPLICATE_DETECTION o una validación equivalente. También verifica que esa opción se aplique al formato JSON real y no sólo al binding posterior.

Un cambio de librería o factory que omite la opción debe hacer fallar el test antes de desplegar.

Go muestra por qué importa la versión

La documentación actual de encoding/json distingue semánticas. La variante histórica v1 permite nombres duplicados, mientras v2 los rechaza salvo una opción que vuelva a habilitarlos.

Ese cambio es saludable para la interoperabilidad, pero revela un riesgo de migración: dos servicios que leen la misma respuesta de infracciones pueden comportarse distinto si usan generaciones diferentes del paquete.

El reporte del laboratorio no dice sólo “Go”. Registra runtime, import, versión semántica, tipo destino y opciones.

Tabla de resultados reproducible

Para cada caso guardamos hash de los bytes de entrada, comando o test, versión y una de tres salidas:

No simplificamos “aceptado” a éxito. Un parser que conserva el último valor falla la política del contrato aunque no arroje excepción.

Los fixtures viven junto a los tests para que una actualización de dependencia vuelva a ejecutarlos.

Rechazo temprano en el servidor

La primera defensa es no emitir duplicados. La respuesta con las actas se genera desde estructuras tipadas y un solo serializador, no concatenando fragmentos de texto. El test inspecciona el stream final, porque dos middleware podrían volver a introducir el mismo campo.

Si la API recibe JSON, el parser del borde se configura para rechazar nombres duplicados antes del binding. La respuesta es un error 400 genérico, sin devolver un payload que puede traer patentes.

El log registra ruta, versión de contrato y posición depurada, nunca patente, token ni cuerpo completo.

OpenAPI no alcanza después del parseo

Un schema puede exigir que el estado del acta pertenezca a un enum y que amount sea numérico. Ambas condiciones se cumplen en el mapa final aunque el texto original tuviera dos valores válidos y contradictorios.

Por eso el test de contrato tiene dos capas: integridad sintáctica estricta sobre el stream y validación semántica del objeto resultante.

También se prueba la respuesta del proveedor antes de transformarla. Validar únicamente la salida propia puede esconder que se eligió arbitrariamente una de dos claves que llegaron del portal.

Campos sensibles para una API de multas

Duplicar status puede convertir parcial en completo. Duplicar total altera agregados. Duplicar jurisdiction atribuye un acta a otra fuente. Duplicar amount cambia una decisión de pago o conciliación.

Para estados se prefieren estructuras explícitas: resultado general, resultados por jurisdicción y errores separados. Pero incluso un buen esquema requiere nombres únicos.

Cuando el proveedor entrega un duplicado, no marcamos “sin multas” ni intentamos adivinar. La fuente queda con error de contrato y el resultado agregado permanece parcial.

Productor y consumidor comparten la defensa

El servidor garantiza que nunca emite duplicados y publica fixtures negativos. El cliente no confía ciegamente: activa rechazo estricto y prueba su runtime. Así un proxy defectuoso o proveedor externo no transforma silenciosamente la decisión.

Las métricas cuentan errores por versión y ruta, sin cuerpos ni patentes. Un aumento después de una actualización dispara revisión antes de relajar la regla.

Los mismos bytes deben significar lo mismo

La interoperabilidad no consiste sólo en que todos acepten el archivo. Consiste en que todos obtengan el mismo significado. Con claves duplicadas, RFC 8259 advierte que eso no está garantizado.

La política más simple es uniforme: no producir, rechazar al leer y mantener tests en cada lenguaje soportado. Perder una consulta con error explícito es preferible a informar dos importes distintos de la misma multa como si fueran uno solo.

Metodología

Alcance
Mini laboratorio con payloads sintéticos que duplican `status`, `amount` y objetos anidados, ejecutados en parsers documentados de JavaScript, Python, Java y Go.
Unidad de análisis
Un vector de bytes JSON, la versión y configuración del parser, y su resultado exacto como valor aceptado o error de clave duplicada.
Cobertura
RFC 8259 y documentación oficial de cada implementación verificadas al 14 de agosto de 2026; no se envían respuestas reales de clientes.

Limitaciones

Fuentes

Preguntas frecuentes

¿Un objeto con dos claves iguales es JSON sintácticamente válido?

La gramática de RFC 8259 lo acepta, pero la norma dice que los nombres deberían ser únicos y que sólo los objetos con nombres únicos son interoperables entre implementaciones.

¿Un schema JSON detecta siempre las claves duplicadas?

No si la validación ocurre después del parseo. Para entonces el mapa puede conservar sólo un valor. La detección debe actuar durante la lectura de tokens o en un parser configurado para rechazar duplicados.

¿Alcanza con decidir que siempre gana la última clave?

No para un contrato entre sistemas heterogéneos. Otro cliente puede rechazar, conservar otra representación o cambiar de comportamiento al actualizar su librería, generando decisiones diferentes con los mismos bytes.

Notas relacionadas