Saltar al contenido principal

Representación de Recursos y Formatos de Datos

🎯 Objetivo

Regla

5.1: Se debe estructurar y formatear las representaciones de los recursos intercambiados de manera consistente y predecible, utilizando JSON como formato predominante. 5.2: Se debe utilizar la negociación de contenido (Accept, Content-Type) de forma estándar.

📖 Concepto y Definición

Regla

5.3: Los clientes deben interactuar con los recursos a través de sus representaciones, que son instantáneas del estado del recurso en un tipo de medio específico. 5.4: El formato de datos predeterminado y obligatorio para todas las APIs REST es JSON (application/json).

  • Otros formatos (XML, etc.) solo deben considerarse bajo justificación explícita y aprobación, y su soporte no exime del soporte obligatorio de JSON. 5.5: La negociación de contenido debe realizarse mediante los encabezados HTTP estándar:
  • Accept (solicitud): El cliente debe usarlo para indicar los tipos de medio que puede entender. La API debe respetarlo.
  • Content-Type (solicitud/respuesta): Debe usarse para indicar el tipo de medio del cuerpo del mensaje. En solicitudes POST/PUT/PATCH con cuerpo, el cliente debe enviarlo. En respuestas con cuerpo, el servidor debe enviarlo.

🤔 ¿Por qué es Importante? / Beneficios Clave (Contexto)

  • Interoperabilidad: JSON es ampliamente soportado.
  • Legibilidad y Facilidad de Uso: JSON es fácil de leer y parsear.
  • Flexibilidad (Limitada por Norma): Aunque se norma JSON, la negociación de contenido permite teóricamente otros formatos.
  • Desacoplamiento: El cliente se desacopla de la estructura interna del recurso.

✅ Buenas Prácticas y Recomendaciones Clave

Para JSON (Formato Obligatorio):

Regla

5.6: Los nombres de las claves (campos) en JSON deben ser descriptivos, en inglés, y seguir la convención camelCase (ej. firstName, totalAmount). Esta convención es obligatoria para toda la API. 5.7: Las fechas y horas deben representarse siempre en formato ISO 8601 string, preferiblemente en UTC (ej. 2023-10-27T10:30:00Z) o con offset (ej. 2023-10-27T12:30:00+02:00). 5.8: Los campos opcionales sin valor deben ser omitidos de la representación JSON. No se debe enviar null para campos opcionales ausentes, a menos que null tenga un significado semántico explícito y documentado para ese campo (ej. "explícitamente sin valor" vs. "valor no proporcionado/desconocido"). Esta distinción debe ser clara. 5.9: Los datos anidados deben estructurarse lógicamente usando objetos y arrays JSON. 5.10: Un campo debe tener siempre el mismo tipo de dato JSON (string, number, boolean, object, array) en todas las instancias de un recurso y entre versiones compatibles de la API. 5.11: Para campos que representan listas o colecciones, si la lista está vacía, se debe devolver un array JSON vacío []. No se debe usar null para representar una colección vacía, a menos que el campo en sí sea opcional y esté ausente (ver Norma 5.8). 5.12: Para campos string, una string vacía "" es un valor válido y distinto de null o un campo omitido. Su significado debe ser claro en el contexto del campo.

Negociación de Contenido:

Regla

5.13: La API debe respetar el encabezado Accept del cliente. Si el cliente solicita application/json (o */* y JSON es el predeterminado), la API debe responder con Content-Type: application/json. 5.14: Si el cliente no especifica Accept o especifica */*, la API debe devolver application/json por defecto. 5.15: Si el cliente solicita un formato explícito (distinto de */*) que la API no soporta (ej. application/xml si solo se soporta JSON), la API debe responder con 406 Not Acceptable. 5.16: El servidor debe incluir siempre el encabezado Content-Type: application/json en las respuestas que tienen cuerpo JSON. 5.17: Se requiere que los clientes envíen Content-Type: application/json en solicitudes POST/PUT/PATCH que contengan un cuerpo JSON. Si no se provee o es un tipo no soportado, la API debe responder con 415 Unsupported Media Type.

General (Estructura de Respuesta):

Regla

5.18: La API debe minimizar los datos transferidos (evitar "over-fetching"). Solo deben devolverse los datos necesarios para el contexto de la solicitud. Se deben considerar mecanismos de selección de campos (ver Sección 10 y 12). 5.19: La estructura general de la respuesta (éxito y error) debe ser consistente y predecible en toda la API, como se detalla a continuación y en la Sección 11 (Manejo de Errores).

Consistencia Integral en Estructuras de Respuesta (Éxito y Error):

Regla

5.20: Importancia Crítica: Una estructura de respuesta consistente es obligatoria para:

  • Asegurar previsibilidad y facilidad de uso.
  • Reducir la carga cognitiva de los desarrolladores.
  • Simplificar el código cliente para procesar respuestas.
  • Facilitar el mantenimiento y la evolución de la API.
  • Mejorar el soporte de herramientas (documentación, SDKs, mocks).

5.21: Consistencia en Respuestas Exitosas:

  • Envoltura de Datos (Data Wrapping):
    • Para un único recurso, la respuesta debe estar envuelta usando la clave "data": { "data": { "id": 1, ... } }.
    • Para una lista de recursos, la respuesta debe estar envuelta. La clave principal debe ser "data" y contendrá el array de recursos.
    • La envoltura permite añadir metadatos (paginación, enlaces HATEOAS) de forma limpia y consistente.
    • Importante: La consistencia en el uso del envelope mínimo es obligatoria para todas las respuestas, facilitando el procesamiento por parte de los clientes y mejorando la mantenibilidad de la API.
  • Nomenclatura de Campos: Se refuerza la Norma 5.6 (camelCase).
  • Formatos de Datos Comunes: Se refuerza la Norma 5.7 (ISO 8601 para fechas) y debe asegurarse consistencia para otros tipos comunes (identificadores, booleanos, números).
  • Estructura para Colecciones y Paginación: Si una respuesta devuelve una lista, la estructura para la paginación (si existe) debe ser idéntica en todos los endpoints que la soporten. Campos como totalItems, totalPages, page, perPage deben tener nombres y significados consistentes y estar documentados (ver Sección 10).
    // Ejemplo de paginación consistente obligatoria
    {
    "data": [ /* ... elementos ... */ ],
    "_links": [
    {
    "rel": "self",
    "href": "https://api.example.com/v1/resource?page=1&perPage=10",
    "method": "GET"
    },
    {
    "rel": "first",
    "href": "https://api.example.com/v1/resource?page=1&perPage=10",
    "method": "GET"
    },
    {
    "rel": "next",
    "href": "https://api.example.com/v1/resource?page=2&perPage=10",
    "method": "GET"
    }
    ],
    "_meta": {
    "timestamp": "2023-10-27T12:00:00Z",
    "version": "1.0.0",
    "pagination": {
    "totalItems": 100,
    "totalPages": 10,
    "page": 1,
    "perPage": 10
    }
    }
    }
  • Enlaces HATEOAS: Si se utiliza HATEOAS (ver Sección 6), la forma en que se representan los enlaces (ej. dentro de un objeto "_links" o "links" a nivel raíz o por recurso) y sus atributos (href, rel, method) debe ser uniforme en toda la API.

5.22: Consistencia en Respuestas de Error:

  • La estructura del cuerpo de una respuesta de error JSON debe ser consistente en toda la API, independientemente del código de estado 4xx o 5xx. Esto es obligatorio y se detalla en la Sección 11.
  • No se debe exponer información sensible (stack traces, etc.) en respuestas de error al cliente.

Estructura General del Diseño de Respuestas API

Regla

5.23: Todas las respuestas API deben seguir una estructura de envelope mínima + HATEOAS consistente, como se detalla a continuación.

Estructura de Respuestas de Éxito (Recurso Único)

Regla

5.24: Las respuestas de éxito para recursos únicos deben seguir esta estructura:

{
"data": {
// Datos del recurso solicitado
},
"_links": [
{
"rel": "self",
"href": "URL_completa_al_recurso",
"method": "GET"
},
{
"rel": "nombre_relacion",
"href": "URL_completa_a_accion_o_recurso_relacionado",
"method": "MÉTODO_HTTP"
}
],
"_meta": {
"timestamp": "ISO-8601-timestamp",
"version": "version_de_API"
}
}

Estructura de Respuestas de Éxito (Colecciones con Paginación)

Regla

5.25: Las respuestas de éxito para colecciones deben seguir esta estructura:

{
"data": [
// Array de recursos
],
"_links": [
{
"rel": "self",
"href": "URL_actual_con_parametros_paginacion",
"method": "GET"
},
{
"rel": "first",
"href": "URL_primera_pagina",
"method": "GET"
},
{
"rel": "prev",
"href": "URL_pagina_anterior",
"method": "GET"
},
{
"rel": "next",
"href": "URL_pagina_siguiente",
"method": "GET"
},
{
"rel": "last",
"href": "URL_ultima_pagina",
"method": "GET"
}
],
"_meta": {
"timestamp": "ISO-8601-timestamp",
"version": "version_de_API",
"pagination": {
"page": numero_pagina_actual,
"perPage": elementos_por_pagina,
"totalPages": total_paginas,
"totalItems": total_elementos
}
}
}

Estructura de Respuestas de Error

Regla

5.26: Las respuestas de error deben seguir esta estructura:

{
"error": {
"status": codigo_HTTP,
"error": "nombre_estandar_error",
"message": "descripcion_error_para_humanos",
"code": "CODIGO_ERROR_INTERNO",
"errors": [
{
"field": "campo_con_error",
"message": "descripcion_especifica_error"
}
]
},
"_meta": {
"timestamp": "ISO-8601-timestamp",
"version": "version_de_API"
},
"_links": [
{
"rel": "self",
"href": "URL_del_recurso",
"method": "MÉTODO_HTTP"
}
]
}

Componentes de la Estructura

Regla

5.27: Los componentes de la estructura deben cumplir con las siguientes especificaciones:

  • data: Contiene los datos del recurso o colección solicitada (solo para respuestas exitosas).
  • error: Contiene la información detallada del error (solo para respuestas de error).
  • _links: Array de enlaces HATEOAS que permiten la navegación y descubrimiento de recursos relacionados.
  • _meta: Metadatos de la respuesta, incluyendo timestamp y versión de la API.
  • pagination: (Solo para colecciones) Información sobre la paginación actual, dentro de _meta.

Principios de Implementación

Regla

5.28: La implementación debe seguir estos principios:

  • Minimalismo: Solo incluir lo necesario en la envelope.
  • Autodescripción: Enlaces HATEOAS claros y descriptivos.
  • Consistencia: Misma estructura en todas las respuestas.
  • Separación: Clara distinción entre datos, enlaces y metadatos.

💡 Ejemplos Prácticos (Ilustrativos)

Ejemplo 1: Representación JSON de un Usuario (Norma 5.6, 5.7, 5.8, 5.11, 5.24)

// GET /users/123
// Content-Type: application/json (Norma 5.16)
{
"data": {
"userId": "123",
"username": "johndoe",
"firstName": "John",
"lastName": "Doe",
"email": "john.doe@example.com",
"isActive": true,
"dateJoined": "2022-01-15T09:00:00Z",
"lastLogin": "2023-10-26T15:45:10Z",
"middleName": null,
"preferences": {
"theme": "dark",
"notificationsEnabled": true
},
"roles": ["user", "editor"],
"tags": []
},
"_links": [
{
"rel": "self",
"href": "https://api.example.com/v1/users/123",
"method": "GET"
},
{
"rel": "update",
"href": "https://api.example.com/v1/users/123",
"method": "PUT"
},
{
"rel": "delete",
"href": "https://api.example.com/v1/users/123",
"method": "DELETE"
},
{
"rel": "orders",
"href": "https://api.example.com/v1/users/123/orders",
"method": "GET"
}
],
"_meta": {
"timestamp": "2023-10-27T12:00:00Z",
"version": "1.0.0"
}
}

Ejemplo 2: Lista de Recursos con Envoltura y Paginación (Norma 5.21, 5.25)

// GET /orders?status=pending&page=1&perPage=10
// Content-Type: application/json
{
"data": [
{
"orderId": "ord_001",
"status": "pending",
"totalAmount": 150.75,
"createdAt": "2023-10-27T11:00:00Z"
},
{
"orderId": "ord_002",
"status": "pending",
"totalAmount": 89.50,
"createdAt": "2023-10-27T11:05:00Z"
}
],
"_links": [
{
"rel": "self",
"href": "https://api.example.com/v1/orders?status=pending&page=1&perPage=10",
"method": "GET"
},
{
"rel": "first",
"href": "https://api.example.com/v1/orders?status=pending&page=1&perPage=10",
"method": "GET"
},
{
"rel": "prev",
"href": "https://api.example.com/v1/orders?status=pending&page=1&perPage=10",
"method": "GET"
},
{
"rel": "next",
"href": "https://api.example.com/v1/orders?status=pending&page=2&perPage=10",
"method": "GET"
},
{
"rel": "last",
"href": "https://api.example.com/v1/orders?status=pending&page=5&perPage=10",
"method": "GET"
}
],
"_meta": {
"timestamp": "2023-10-27T12:00:00Z",
"version": "1.0.0",
"pagination": {
"page": 1,
"perPage": 10,
"totalPages": 5,
"totalItems": 42
}
}
}

Ejemplo 3: Respuesta de Error (Norma 5.26)

// POST /users
// Content-Type: application/json
// Status: 400 Bad Request
{
"error": {
"status": 400,
"error": "ValidationError",
"message": "Los datos proporcionados no son válidos",
"code": "INVALID_USER_DATA",
"errors": [
{
"field": "email",
"message": "El formato del correo electrónico no es válido"
},
{
"field": "password",
"message": "La contraseña debe tener al menos 8 caracteres"
}
]
},
"_meta": {
"timestamp": "2023-10-27T12:00:00Z",
"version": "1.0.0"
},
"_links": [
{
"rel": "self",
"href": "/v1/users",
"method": "POST"
}
]
}

🛠️ Herramientas y Consideraciones Adicionales (Contexto)

  • JSON Schema: Se recomienda su uso para definir y validar la estructura de las representaciones JSON (solicitudes y respuestas).
  • Tipos de Medios Personalizados: Para versionado o APIs muy específicas, se pueden definir tipos de medios propios (ej. application/vnd.example.user.v1+json), pero deben basarse en JSON.
  • Datos Binarios: Para transferir archivos, se deben usar tipos como image/jpeg, application/pdf, o application/octet-stream. La transferencia puede ser directa o usando multipart/form-data. Esto debe estar claramente documentado.
  • Compresión: Se debe usar compresión HTTP (Gzip, Brotli) para reducir el tamaño de las representaciones y mejorar el rendimiento. El cliente indica soporte con Accept-Encoding y el servidor responde con Content-Encoding.