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 ·
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:
- aceptado con último valor;
- rechazado por nombre duplicado;
- aceptado conservando la secuencia completa.
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
- El comportamiento depende de versión, destino de binding y opciones, por lo que cada integración debe ejecutar los fixtures en su stack real.
- La nota cubre nombres duplicados y no reemplaza límites de tamaño, profundidad, Unicode o validación completa del contrato.
- La tabla describe configuraciones probadas y documentadas, no todas las librerías disponibles en cada lenguaje.
Fuentes
- RFC 8259 — The JavaScript Object Notation Data Interchange Format — RFC Editor (consultada el )
- ECMAScript Language Specification — JSON.parse — Ecma International TC39 (consultada el )
- Python 3.14 json — Standard Compliance and Interoperability — Python Software Foundation (consultada el )
- Go encoding/json package documentation — The Go Authors (consultada el )
- Jackson Core — parser and strict duplicate detection — FasterXML (consultada el )
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
Cómo versionar una respuesta cuando cada jurisdicción cambia
Una respuesta agregada necesita distinguir versión de contrato, revisión de datos y estado de cada fuente. Así un cambio municipal no obliga a reinterpretar silenciosamente todo el resultado.
Timeout no significa “sin multas”: cómo informar resultados parciales en una integración
Cuando una fuente no responde, el resultado es parcial o indeterminado, nunca cero. Un contrato explícito permite reintentar, alertar y decidir sin producir falsos negativos.
API key para consultar multas: alcance y rotación
Una clave no dice quién llama: dice qué puede hacer quien la tenga. Declarar operaciones, datos, entorno y caducidad al emitirla es lo que después vuelve barata una rotación.