Leer JSON
Los seis tipos de JSON y los valores que hacen tropezar a los tests.
La mayoría de las APIs que probarás envían y reciben JSON. Es un formato de texto para datos estructurados, y es lo bastante pequeño para aprenderlo en una tarde. La sintaxis rara vez es el problema. El problema es que JSON deja algunas cosas sin definir, y cada programa rellena esos huecos a su manera. En esos huecos viven muchos bugs, así que esta lección les dedica más tiempo que a la sintaxis.
Seis tipos de valor
Un valor JSON siempre es uno de estos:
| Tipo | Ejemplo | Notas |
|---|---|---|
| Objeto | { "key": "value" } | Campos con nombre entre llaves. Las claves son siempre cadenas entre comillas dobles. |
| Array | [1, 2, 3] | Una lista ordenada entre corchetes. Los elementos pueden ser de cualquier tipo, mezclados. |
| Cadena | "hello" | Texto entre comillas dobles. Las comillas simples no están permitidas. |
| Número | 42, -7, 3.14159, 6.022e23 | Un solo tipo para enteros y decimales. Sin ceros a la izquierda, sin NaN, sin Infinity. |
| Booleano | true, false | En minúsculas, sin comillas. |
| Null | null | En minúsculas, sin comillas. Significa “sin valor”. |
Ese es todo el sistema de tipos. No hay tipo fecha, no hay un tipo entero separado de los decimales y no hay forma de añadir comentarios. Esta API tiene un endpoint que devuelve uno de cada:
/api/edge-cases/types{
"string": "hello",
"emptyString": "",
"integer": 42,
"negativeInteger": -7,
"float": 3.14159,
"exponent": 6.022e23,
"booleanTrue": true,
"booleanFalse": false,
"nullValue": null,
"array": [1, 2, 3],
"emptyArray": [],
"object": { "key": "value" },
"emptyObject": {},
"arrayOfObjects": [ { "id": 1, "name": "Alice" }, { "id": 2, "name": "Bob" } ],
"nestedArrays": [[1, 2], [3, [4, [5]]]],
"stringThatLooksLikeNumber": "123",
"stringThatLooksLikeBoolean": "true",
"stringThatLooksLikeNull": "null"
}Mira los tres últimos campos. "123", "true" y "null" son todos cadenas, porque van entre comillas. Una persona que lee la respuesta ve un número, un booleano y un null. Un programa ve tres cadenas. Cuando compruebes una respuesta, comprueba el tipo además del valor. Una API que un día devuelve "price": "249.99" y al siguiente "price": 249.99 ha cambiado su contrato, aunque el número parezca el mismo.
Señalar un valor
Cuando escribes un test o un informe de bug, necesitas una forma de decir a qué valor te refieres. Esta es parte del producto de la primera lección:
{
"id": 1,
"title": "Wireless Noise-Cancelling Headphones",
"price": 249.99,
"category": "electronics",
"rating": { "rate": 4.3, "count": 127 },
"inStock": true,
"createdAt": "2025-06-12T10:30:00Z"
}La notación informal habitual usa un punto para entrar en un objeto y corchetes con un número para elegir un elemento de un array. Se empieza a contar desde cero.
| Ruta | Valor |
|---|---|
title | "Wireless Noise-Cancelling Headphones" |
rating.rate | 4.3 |
rating.count | 127 |
Cuando toda la respuesta es un array, como en /api/Products?limit=2, el ID del primer producto es [0].id y la valoración del segundo es [1].rating.rate. Cada herramienta lo escribe un poco distinto. JSONPath, que verás en Postman, REST Assured y muchas librerías de tests, empieza con un signo de dólar para la raíz: $.rating.rate, $[0].id. También tiene comodines, así que $[*].pricesignifica "el precio de cada elemento". JMESPath, que usan las herramientas de línea de comandos de AWS, escribe lo mismo como [*].price. Todavía no necesitas aprender ninguno a fondo. Saber que $[0].idsignifica "el id del primer elemento" basta para leer la mayor parte del código de tests.
Null, ausente y vacío
Se parecen y significan cosas distintas. Supón que un usuario no tiene segundo nombre. Una API podría decirlo al menos de cinco maneras:
"middleName": null, el campo existe y no tiene valor- ninguna clave
middleName "middleName": "", una cadena vacía"middleName": "null", la palabra null de cuatro letras, normalmente un bug en el que algo llamó a toString sobre un null"middleName": "None"o"undefined", el mismo bug desde Python o JavaScript
Este endpoint los devuelve todos, uno al lado del otro:
/api/edge-cases/nulls{
"nullValue": null,
"nestedNull": { "a": null, "b": { "c": null } },
"arrayOfNulls": [null, null, null],
"arrayWithNull": [1, null, 3],
"nullAsString": "null",
"undefinedAsString": "undefined",
"noneAsString": "None",
"emptyString": "",
"emptyArray": [],
"emptyObject": {},
"zero": 0,
"false": false
}Cuál es el correcto depende de la documentación de la API, y la documentación a menudo no lo dice. Lo que sí puedes probar es la coherencia. Si un producto tiene "discount": null y otro no tiene clave discount, un cliente que lea product.discount.amount fallará con uno y no con el otro. Vigila también que no se usen 0 y falsepara decir "desconocido". Un stock de 0 y un stock que nadie conoce son hechos distintos.
Números
JSON no pone límite al tamaño ni a la precisión de un número. Los programas que leen JSON sí. JavaScript guarda todos los números como floats de 64 bits, que solo pueden representar enteros con exactitud hasta 9.007.199.254.740.991. Por encima, redondea en silencio.
/api/edge-cases/numbers{
"int64Max": 9223372036854775807,
"int64MaxAsString": "9223372036854775807",
"maxSafeInteger": 9007199254740991,
"beyondSafeInteger": 9007199254740993,
"beyondSafeIntegerAsString": "9007199254740993",
"pointOnePlusPointTwo": 0.30000000000000004,
"money": 19.99,
"moneyAsString": "19.99",
"trailingZeros": 1.50,
"negativeZero": -0,
"numberAsString": "42",
"nanAsString": "NaN"
}Analiza esa respuesta en JavaScript y en Python y compara:
JSON.parse(body).beyondSafeInteger // 9007199254740992
JSON.parse(body).int64Max // 9223372036854776000json.loads(body)["beyondSafeInteger"] # 9007199254740993
json.loads(body)["int64Max"] # 9223372036854775807JavaScript convirtió 9007199254740993 en 9007199254740992 sin ningún error. Python lo mantuvo exacto. Si una API usa IDs enteros de 64 bits (los de Twitter lo eran, y también los de muchas bases de datos), un cliente JavaScript puede acabar pidiendo el registro equivocado. Por eso las APIs cuidadosas envían los IDs grandes como cadenas, como int64MaxAsString arriba. Cuando pruebes una API con IDs numéricos grandes, comprueba qué recibe de verdad un cliente en el navegador.
Los decimales tienen un problema parecido. 0.1 + 0.2 es 0.30000000000000004 en casi todos los lenguajes, porque los floats binarios no pueden representar 0.1 con exactitud. Con dinero, esto importa. Algunas APIs envían los precios como números (19.99), otras como cadenas ("19.99") y otras como céntimos enteros (1999). Cualquiera puede funcionar. Un test debería comprobar que los totales cuadran al céntimo, no que se "acercan lo suficiente".
Dos cosas menores de la misma respuesta. 1.50 y 1.5 son el mismo número una vez analizados, así que un test que compara texto en bruto fallará donde uno que compara valores pasa. Y -0 es JSON válido, pero JSON.stringify(-0) de JavaScript escribe 0, así que no sobrevive a un viaje de ida y vuelta.
Las fechas son cadenas
JSON no tiene tipo fecha, así que las fechas viajan como cadenas o números, y cada API elige su propio formato. Este es un mismo instante escrito de varias formas:
/api/edge-cases/dates{
"iso8601Utc": "2025-07-15T09:30:00Z",
"iso8601PositiveOffset": "2025-07-15T15:00:00+05:30",
"iso8601NoZone": "2025-07-15T09:30:00",
"dateOnly": "2025-07-15",
"unixSeconds": 1752571800,
"unixMilliseconds": 1752571800000,
"usFormat": "07/15/2025",
"euFormat": "15/07/2025",
"invalidLeapDay": "2025-02-29T00:00:00Z",
"nullDate": null,
"emptyDate": ""
}El formato que más verás es ISO 8601 con zona, como 2025-07-15T09:30:00Z. La Z significa UTC. Cosas que comprobar cuando una API devuelve fechas:
- La zona está presente.
2025-07-15T09:30:00sin zona significa las 9:30 en algún sitio, y cada cliente adivinará un sitio distinto. - Las marcas de tiempo Unix dejan claro si son segundos o milisegundos.
1752571800y1752571800000son el mismo instante, y confundirlos te lleva a 1970 o al año 57500. 07/15/2025y15/07/2025solo son inequívocos porque 15 no puede ser un mes.03/04/2025es marzo o abril según quién lo lea.- Algunos analizadores aceptan fechas imposibles como
2025-02-29y otros las rechazan. Prueba a enviarlas a una API que reciba fechas como entrada.
Orden de las claves y claves duplicadas
Las claves de un objeto no tienen orden. {"a": 1, "b": 2} y {"b": 2, "a": 1} son el mismo objeto. La mayoría de los servidores envían las claves en el mismo orden siempre, así que tienta comparar la respuesta entera como cadena. Ese test se rompe el día en que una actualización de una librería reordena las claves, sin que haya nada mal en realidad. Compara valores analizados. El orden de los arrays, en cambio, sí importa: [1, 2] y [2, 1] son arrays distintos.
Las claves duplicadas son un caso más raro. El estándar JSON dice que las claves "deberían" ser únicas, no que deban serlo, así que técnicamente esto está permitido:
/api/edge-cases/duplicate-keys{
"id": 1,
"name": "first value",
"name": "second value",
"nested": { "flag": true, "flag": false },
"id": 2
}JSON.parse de JavaScript y json.loads de Python se quedan los dos con el último valor, lo que da {"id": 2, "name": "second value", "nested": {"flag": false}}. Otros analizadores se quedan con el primer valor o rechazan el documento. Si un gateway comprueba la primera clave role y el backend lee la segunda, esa diferencia se convierte en un agujero de seguridad. No te encontrarás claves duplicadas a menudo en las respuestas, pero vale la pena enviarlas en las peticiones cuando pruebes cómo se maneja la entrada.
Cuando el JSON está roto
A veces un servidor envía algo que ni siquiera es JSON válido: un cuerpo truncado, una página de error HTML, una traza de pila. Esta es una respuesta que dice ser JSON y no lo es:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"id": 1, "name": "Alice", "tags": ["a", "b",], "active": truHay una coma final después de "b", y el cuerpo se corta en mitad de true. El estado es 200 y la cabecera Content-Type dice application/json, así que un cliente intentará analizarlo y fallará. Node.js informa Unexpected token ']'. Python informa Illegal trailing comma before end of array. En una API real, esto suele significar que el servidor se cayó a mitad de escribir la respuesta, o que un proxy la cortó. La lección para los tests: analiza el cuerpo en cada comprobación. Un 200 con un cuerpo que no se puede analizar es un fallo, y un test que solo mire el código de estado no lo verá.
Leer una respuesta grande
Las respuestas reales a menudo tienen cientos de líneas. Antes de comprobar detalles, hazte una idea de la forma: si el nivel superior es un objeto o un array, cuántos elementos hay, qué campos tiene un elemento. Luego lee un elemento completo. La mayoría de los bugs de JSON se ven solo con la forma, como una lista que vuelve como un único objeto cuando hay un solo resultado, o un objeto envuelto en una clave data extra en un endpoint y no en otro.
Ejercicio
Envía GET /api/Countries/code/INinicia sesión para verlo y responde a estas preguntas a partir de la respuesta, escribiendo cada ruta con la notación de puntos de esta lección:
- ¿De qué tipo es
population, y de qué tipo escallingCode? ¿Por qué podrían ser distintos? - ¿Cuál es la ruta al símbolo de la moneda?
- ¿Cuál es la ruta al segundo idioma?
- El campo
flages un emoji. Mira la respuesta en bruto con curl y compárala con lo que muestra tu navegador. ¿Qué es distinto, y le importa a un cliente?
Después abre /api/edge-cases/stringsinicia sesión para verlo y encuentra al menos dos valores que se verían bien en un navegador pero podrían romper un programa que los guarde o los muestre.