Dev Tools

Buenas Prácticas de JSON para el Desarrollo de APIs: Respuestas, Errores y Rendimiento

Introducción: JSON Es la Lengua Franca de las APIs Web — Pero un JSON Mal Estructurado Causa Bugs

JSON es suficientemente simple para que cualquier desarrollador pueda empezar a usarlo de inmediato. Esa simplicidad es también su trampa. Dado que no hay estructura forzada más allá de la sintaxis básica, cada equipo termina con convenciones sutilmente diferentes: algunos usan claves en camelCase, otros en snake_case; algunos envuelven las respuestas en un envelope data, otros devuelven objetos directos; algunos usan fechas ISO 8601, otros usan timestamps Unix; algunos devuelven null para campos vacíos, otros omiten el campo por completo. Estas inconsistencias se multiplican en bugs del lado del cliente, fallos de integración y horas de depuración.

Esta guía cubre las convenciones que hacen las APIs JSON predecibles, mantenibles y seguras. Los principios aplican independientemente del framework o lenguaje. Usa nuestro formateador JSON para inspeccionar, validar y depurar tus respuestas de API mientras implementas estos patrones.

Envoltorios de Respuesta Consistentes

Un envoltorio de respuesta es un objeto wrapper estándar al que se ajusta cada respuesta de la API. Sin uno, una respuesta exitosa podría ser un objeto directo, un array o una cadena dependiendo del endpoint, forzando a los clientes a manejar cada caso de forma diferente.

Un patrón simple y ampliamente adoptado:

{
"data": { ... },
"error": null,
"meta": {
"requestId": "a1b2c3",
"timestamp": "2026-03-17T12:00:00Z"
}
}

En caso de éxito, data contiene el payload de respuesta y error es null. En caso de fallo, data es null y error contiene los detalles del error. El objeto meta transporta metadatos a nivel de petición — IDs de petición para trazabilidad, timestamps, información de rate limiting y datos de paginación para respuestas de listados.

Este patrón significa que los clientes siempre pueden verificar response.error !== null para detectar fallos, en lugar de depender exclusivamente de los códigos de estado HTTP que pueden ser suprimidos por proxies o oscurecidos por el manejo de errores del framework. También crea un lugar claro para añadir concerns transversales (versionado, trazabilidad, advertencias de deprecación) sin romper las formas de respuesta existentes.

Convenciones de Nombres: Elige Una y No te Desvíes Nunca

Las dos convenciones de nombres de claves JSON más comunes para APIs son camelCase (ej. firstName, createdAt) y snake_case (ej. first_name, created_at). camelCase es la elección natural para clientes JavaScript ya que coincide con la nomenclatura de variables JavaScript. snake_case es común en APIs Python y Ruby. Ninguna es universalmente superior — lo que importa es la consistencia absoluta dentro de una API.

Reglas que previenen los errores de nomenclatura más comunes:

  • Elige una convención y aplícala en todas partes. Un linter (ESLint con regla camelcase o un JSON Schema personalizado) debe aplicar esto automáticamente.
  • Usa pluralización consistente. Los arrays son siempre plurales (usuarios, elementos), los objetos individuales son siempre singulares (usuario, elemento). Nunca mezcles lista_usuarios con elementos.
  • Evita las abreviaciones. numRegistros vs totalRegistros vs total — tres nombres diferentes para el mismo concepto. Usa la palabra completa y elige una.
  • Los nombres booleanos deben leerse naturalmente como una pregunta sí/no. isActive, hasPermission, canEdit en lugar de active, permission, editable.

Formato de Respuesta de Error: RFC 7807 Problem Details

El RFC 7807 "Problem Details for HTTP APIs" define un formato JSON (y XML) estándar para las respuestas de error. Usarlo significa que los clientes pueden predecir exactamente dónde encontrar la información de error independientemente del endpoint que lo produjo.

Una respuesta de error completamente conforme al RFC 7807:

{
"type": "https://api.ejemplo.com/errors/validation",
"title": "Validación Fallida",
"status": 422,
"detail": "El cuerpo de la petición contiene campos inválidos.",
"instance": "/usuarios/crear",
"errors": [
{"field": "email", "message": "Debe ser una dirección de email válida"},
{"field": "edad", "message": "Debe ser un entero positivo"}
]
}

Campos clave: type es una URI que identifica de forma única la clase de error (enlaza a documentación); title es un resumen corto legible por humanos; status refleja el código de estado HTTP; detail proporciona contexto accionable; instance identifica la petición específica que falló. La extensión del array errors (no en el RFC pero ampliamente adoptada) proporciona errores de validación a nivel de campo para formularios.

Nunca expongas detalles de errores internos (stack traces, errores SQL, IDs internos) en las respuestas de error. Regístralos en el servidor y devuelve solo una URI type estable y un detail legible por humanos al cliente.

Fechas y Horas: Siempre ISO 8601 con Zona Horaria

El manejo de fechas y horas es la fuente de un número desproporcionado de bugs en APIs. Las reglas son simples y no negociables para cualquier API usada en múltiples zonas horarias:

  • Siempre usa el formato ISO 8601: "2026-03-17T12:00:00Z" para UTC, o "2026-03-17T14:00:00+02:00" para un offset explícito.
  • Siempre incluye información de zona horaria. Un timestamp sin zona horaria ("2026-03-17T12:00:00") es ambiguo — los clientes en diferentes zonas horarias lo interpretan de forma diferente. Usa siempre Z (UTC) o un offset numérico.
  • Prefiere UTC para almacenamiento y transmisión. Convierte a hora local en el cliente. Esto hace que la ordenación, comparación y aritmética sean trivialmente correctas.
  • Evita los timestamps Unix en las respuestas. Los timestamps Unix (segundos desde el epoch) son compactos pero no legibles por humanos, no son autodocumentables y requieren conocimiento implícito de si el valor está en segundos o milisegundos (un bug común). Las cadenas ISO 8601 son inequívocas y parseables por cualquier lenguaje moderno sin código personalizado.
  • Valores solo de fecha: Cuando el tiempo es genuinamente irrelevante (una fecha de nacimiento, una fecha de factura), usa "2026-03-17" (solo fecha, sin hora ni zona horaria) para señalar que el valor es una fecha de calendario, no un punto en el tiempo.

Null vs Ausente: Cuándo Incluir Campos

Una decisión de diseño frecuente es si incluir una clave con valor null u omitir la clave por completo cuando el valor no está disponible. Ambas aproximaciones son JSON técnicamente válido, pero tienen implicaciones semánticas diferentes:

  • Incluir con null cuando el campo es esperado pero no tiene valor. Ejemplo: "segundoNombre": null en un perfil de usuario — el campo es parte del esquema, el usuario simplemente no tiene segundo nombre. Los clientes pueden confiar en que el campo está presente y manejar null explícitamente.
  • Omitir el campo cuando es condicionalmente presente. Ejemplo: una direccionEnvio en un pedido que usa recogida en tienda. Omitirlo distingue "campo no aplicable" de "campo es null."

La regla crítica: ser consistente. Si un campo aparece en algunas respuestas pero no en otras sin un patrón discernible, los clientes deben verificar defensivamente hasOwnProperty para cada campo opcional, o simplemente dar error cuando el campo falta. Documenta qué campos están siempre presentes y cuáles son condicionales.

Paginación: Cursor vs Offset

Cualquier endpoint que pueda devolver más elementos de los que caben en una sola respuesta debe ser paginado. Las dos estrategias dominantes tienen compromisos diferentes:

Paginación por Offset

{"data": [...], "meta": {"total": 1500, "offset": 0, "limit": 25}}

Simple de implementar y de navegar (saltar a cualquier página). La desventaja: inestable ante inserciones y eliminaciones. Si un usuario solicita la página 2 y se insertó un nuevo elemento en la página 1 desde la primera petición, la página 2 ahora contiene un duplicado del último elemento de la página 1. Las consultas de conteo total pueden ser costosas en datasets grandes.

Paginación por Cursor

{"data": [...], "meta": {"nextCursor": "eyJpZCI6MTUwfQ==", "hasMore": true}}

El cursor codifica la posición del último elemento visto (típicamente su ID o timestamp de creación). La siguiente petición obtiene elementos después de ese cursor. La paginación por cursor es estable — las inserciones y eliminaciones no causan duplicados ni saltos. No soporta acceso aleatorio (no se puede saltar directamente a la página 7 sin iterar a través de las páginas 1–6). La paginación por cursor se prefiere para feeds en tiempo real, desplazamiento infinito y datasets grandes.

Versionado de APIs

Las APIs cambian. Los clientes no siempre pueden actualizarse simultáneamente. El versionado es la manera de hacer cambios disruptivos manteniendo funcionando los clientes existentes.

Versionado por Ruta URL

https://api.ejemplo.com/v1/usuarios

La versión es explícita en la URL. Fácil de probar en un navegador, fácil de enrutar en el balanceador de carga, inequívoco en los logs. La desventaja: técnicamente no es RESTful (la URL identifica un recurso, no una versión de un recurso). Este es el enfoque más pragmático y el más ampliamente adoptado en la práctica.

Versionado por Cabecera

Accept: application/vnd.example.api+json; versión=1

Más limpio desde la perspectiva purista REST. Más difícil de probar manualmente, menos visible en logs, requiere middleware para parsear. Adecuado para plataformas de API maduras con cadenas de herramientas de cliente sofisticadas.

Independientemente de la estrategia: distingue los cambios disruptivos (campo eliminado, tipo cambiado, endpoint eliminado) de los no disruptivos (nuevo campo opcional, nuevo endpoint). Solo los cambios disruptivos requieren un incremento de versión. Proporciona un período de deprecación mínimo de 12 meses para las migraciones de versiones mayores.

Seguridad: Prácticas JSON Defensivas

El propio JSON no es un vector de ataques de inyección — es solo texto. Pero el manejo descuidado de JSON permite vulnerabilidades serias:

  • Nunca expongas identificadores internos innecesariamente. Los IDs auto-incrementales de la base de datos revelan el conteo de registros y permiten ataques de enumeración. Siempre que sea posible, usa UUIDs u otros identificadores opacos en las respuestas de la API.
  • Sanitiza el contenido generado por usuarios antes de incluirlo en respuestas JSON. Si un usuario envió una cadena maliciosa y esta se incluye en una respuesta de API que un cliente renderiza como HTML, el cliente puede ser vulnerable a XSS. La codificación JSON no sustituye la sanitización HTML.
  • Establece Content-Type: application/json explícitamente. Nunca dejes que el cliente detecte el tipo de respuesta automáticamente. Algunos navegadores ejecutan JS embebido en respuestas parecidas a JSON si el tipo de contenido es ambiguo.
  • Establece X-Content-Type-Options: nosniff para prevenir la detección automática del tipo de contenido en navegadores.
  • Valida el JSON entrante contra un esquema. Rechaza peticiones con campos inesperados cuando additionalProperties: false está establecido en tu esquema. Esto previene vulnerabilidades de asignación masiva donde un cliente establece campos a los que no debería tener acceso (ej. incluir "role": "admin" en una petición de actualización de usuario).

Rendimiento: Tamaño, Compresión y Respuestas Parciales

Para APIs de alto tráfico, el tamaño del payload JSON impacta directamente en los costos de ancho de banda y la latencia de respuesta.

Minificación y Compresión

Minifica las respuestas JSON en producción (elimina todos los espacios en blanco innecesarios). Habilita la compresión gzip o brotli en el servidor o proxy inverso. Brotli logra una compresión 15–25% mejor que gzip para texto. Con la compresión habilitada, la diferencia entre JSON bien formateado y minificado desaparece en gran medida, pero la minificación aún reduce el costo de parseo en memoria y beneficia a los clientes que no envían Accept-Encoding: gzip.

Respuestas Parciales

Para recursos con muchos campos, permite que los clientes soliciten solo los campos que necesitan: GET /usuarios/1?fields=id,email,nombre. La respuesta contiene solo los campos solicitados. Esto es especialmente valioso para clientes móviles en conexiones con datos medidos. Implementa con un parámetro de consulta fields y una lista blanca de nombres de campos permitidos.

ETags y Peticiones Condicionales

Para recursos con muchas lecturas que cambian con poca frecuencia, implementa ETags. El servidor devuelve ETag: "a1b2c3" con cada respuesta. Los clientes envían If-None-Match: "a1b2c3" en peticiones posteriores. Si el recurso no ha cambiado, el servidor devuelve 304 Not Modified con un cuerpo vacío, ahorrando ancho de banda por completo.

Pruebas de APIs JSON: Validación de Esquema y Pruebas de Contrato

Las pruebas manuales de respuestas de APIs JSON no escalan. Tres enfoques automatizados cubren diferentes capas:

Validación de JSON Schema en CI

Define JSON Schemas para cada tipo de petición y respuesta. En tu pipeline de CI, ejecuta pruebas de integración que validen las respuestas reales de la API contra sus esquemas usando ajv (Node.js) o equivalente. Un fallo de CI cuando cambia el tipo de un campo o se elimina un campo requerido detecta los cambios disruptivos antes de que lleguen a producción.

Colecciones de Postman

Los scripts de prueba de Postman soportan aserciones JavaScript contra los cuerpos de respuesta. Mantén una colección de llamadas críticas a la API con aserciones que verifiquen la presencia de campos, tipos y rangos de valores. Exporta la colección a un archivo postman_collection.json comprometido en el repositorio y ejecútalo en CI vía el runner Newman.

Pruebas de Contrato

Pact es un framework de pruebas de contrato dirigido por el consumidor. El cliente de la API define el contrato (qué campos espera en las respuestas), y el servidor verifica que puede cumplir ese contrato. Las pruebas de contrato detectan cambios disruptivos desde la perspectiva del consumidor — más práctico que intentar anticipar todos los clientes al hacer cambios del lado del servidor.

Usando Nuestro Formateador JSON Durante el Desarrollo de APIs

Nuestro formateador y validador JSON se integra de forma natural en los flujos de trabajo de desarrollo de APIs:

  • Depurar respuestas de API: Pega una respuesta de API sin procesar para embellecerla y validarla instantáneamente. Las estructuras anidadas se vuelven legibles en segundos en lugar de minutos de parseo manual.
  • Verificar cuerpos de petición: Antes de enviar una petición compleja, formatea para verificar que la estructura es correcta. Detectar una coma faltante antes de la llamada a la API ahorra un round trip y un mensaje de error a menudo incomprensible.
  • Comparar antes y después: Minifica ambas versiones de una respuesta para comparar tamaños y verificar que un paso de minificación no corrompió la estructura de datos.

Todas las operaciones se ejecutan en tu navegador. Ningún JSON se envía a ningún servidor — importante cuando se trabaja con credenciales de API de producción o datos de identificación de usuarios en los payloads de respuesta.

← Volver al Blog