1. Los nombres de los campos JSON como contrato
Cuando comenzamos a diseñar JSON, es muy fácil caer en la actitud de "qué importa cómo se llame el campo, lo importante es que los datos sean correctos". Pero en el mundo del backend, es como decir: "qué importa cómo se etiqueten los cables en el cuadro eléctrico, lo importante es que la electricidad esté ahí". Hasta que no hay que arreglarlo, parece que realmente da igual. Pero luego llega el momento de "¿por qué salta chispas solo los viernes?".
Hablando estrictamente, el nombre de un campo en JSON es parte del contrato de la API. El cliente (Postman, frontend, otra aplicación, incluso su futuro cliente Java) no lee sus pensamientos, sino cadenas específicas: externalId, status, items. Si renombra externalId a external_id, el cliente no "adivinará" que es lo mismo. Simplemente no encontrará el campo. Y no porque el cliente sea tonto, sino porque el contrato es precisamente un acuerdo en símbolos.
Se puede imaginar así:
flowchart LR
Client["Cliente (alguien externo)"] -->|"JSON con campos"| Server["Servidor/aplicación"]
Server -->|"JSON con campos"| Client
En este intercambio no hay "magia de comprensión". Solo hay coincidencia de expectativas. Por eso introducimos convenciones de nomenclatura: para que las expectativas sean estables y no dependan del gusto del autor ("hoy me gusta snake_case, mañana CamelCase y drama").
2. Estilo uniforme: camelCase y snake_case
Casi todo principiante tiene una pregunta interna: "¿Cómo es correcto: externalId o external_id?". El secreto es que en la industria se encuentran ambos, y ambos pueden ser "correctos", según el equipo, el ecosistema, los estándares de la empresa y las razones históricas. El problema no comienza cuando eliges el estilo "equivocado", sino cuando eliges dos estilos simultáneamente.
Comparemos las opciones de forma muy práctica en una tabla, sin guerras ni rencores:
| Estilo | Ejemplo | Dónde se encuentra más a menudo | Problema típico |
|---|---|---|---|
| camelCase | externalId | Ecosistema Java/Spring, muchas API JSON | Los principiantes a veces escriben externalID (con dos mayúsculas) y obtienen un "zoológico" |
| snake_case | external_id | Python, parte de las API públicas, algunas guías REST | En el código Java los campos suelen ser camelCase, y sin una regla clara es fácil que empiece la confusión |
| UPPER_CASE | STATUS | Casi en ninguna parte como contrato JSON | Parece un grito y rápidamente se convierte en caos |
Para nuestro proyecto educativo ReadLater Starter mantendremos camelCase en JSON, porque:
- es más fácil coincidir con los nombres de campo Java habituales en DTO y no generar "trabajo manual de traducción" adicional;
- hace que los ejemplos sean más claros al principio del camino (ya estamos aprendiendo muchas cosas nuevas).
Este es un ejemplo de una respuesta "normal" de un elemento de la lista de lectura en este estilo:
{
"id": 1,
"title": "Clean Code",
"author": "Robert C. Martin",
"status": "PLANNED",
"externalId": "OL12345M",
"comment": "Encontrar edición impresa"
}
Y este es un ejemplo de cómo se ve el contrato cuando el estilo deambula por los campos como un gato sobre el teclado:
{
"id": 1,
"bookTitle": "Clean Code",
"external_id": "OL12345M",
"STATUS": "PLANNED"
}
Técnicamente, "también es JSON". Pero como contrato es malo: el cliente tiene que recordar tres reglas de nomenclatura diferentes al mismo tiempo, y cada nuevo campo será una "lotería".
3. Un significado – un nombre: diccionario de ReadLater
Una vez elegido el estilo (camelCase), el siguiente dolor son los sinónimos. Los principiantes suelen llamar a lo mismo con diferentes palabras: bookId, externalId, catalogId, idFromProvider. O state y status. O list y items. Y luego ellos mismos no pueden entender dónde está el "campo real" y dónde el "casi lo mismo pero diferente".
Para evitarlo, es útil crear un pequeño diccionario del proyecto: un conjunto de palabras que usamos de forma estable. No es burocracia, sino proteger el cerebro del sobrecalentamiento. Para ReadLater Starter podemos acordar lo siguiente:
| Significado | Nombre del campo | Ejemplo | Comentario |
|---|---|---|---|
| Identificador interno del elemento de la lista de lectura | id | 42 | Aparece en la response, normalmente no es necesario en la create‑request |
| Identificador del libro en un catálogo externo | externalId | "OL12345M" | Es un eje de identidad diferente, no confundir con id |
| Título del libro | title | "Clean Code" | No bookTitle ni name, si acordamos usar title |
| Autor | author | "Robert C. Martin" | En un curso simple es suficiente con una cadena |
| Estado de lectura | status | "PLANNED" | Mejor una sola palabra que state, readingState, bookStatus mezclados |
| Comentario del usuario | comment | "Comprar en papel" | Campo opcional |
| Elementos de la lista en la respuesta de colección | items | [ ... ] | Nombre en plural para el array |
| Cantidad de elementos | count | 3 | Contador junto a la lista |
El sentido de esta tabla es que no intentamos "inventar nombres ideales para siempre". Simplemente elegimos un término para un significado y lo mantenemos. Esto ahorra tiempo y nervios más de lo que parece.
Ahora un ejemplo del lado Java (todavía sin serialización, solo forma DTO). Comparemos una situación buena y una mala.
Mala: en un DTO "id", en otro "bookId", en un tercero "externalID" – y todo sobre lo mismo.
class BadReadingItemResponse {
long id; // identificador interno del elemento en nuestro servicio
String bookId; // no está claro: ¿es id interno o externo?
String externalID; // otra escritura de "id externo": rompe el diccionario uniforme
}
Buena: id – interno, externalId – externo. Y en todas partes igual.
class ReadingItemResponse {
long id; // id interno del elemento (generado por nuestro servicio)
String externalId; // identificador externo del catálogo (otro significado, no confundir con id)
String title; // título del libro
}
Sí, son "solo nombres". Pero son esos nombres que luego estarán en JSON y que cualquier cliente verá.
4. Nombres de listas: items y count
Cuando haces por primera vez un endpoint "obtener lista", surge la tentación de devolver simplemente un array. Como "para qué complicar". Y a veces realmente está bien. Pero en cuanto aparece la necesidad de devolver junto al array algo más (por ejemplo, count), empieza el "¿y dónde pego esto ahora?".
Por eso, en el proyecto nos acostumbramos de antemano a una forma simple: envoltorio de lista. Y aquí de nuevo los nombres son importantes: lo más frecuente es que te encuentres con items (elementos) y count (su cantidad). Sí, suena trivial. Y eso es bueno: trivial = predecible.
Buena respuesta para una lista de lectura:
{
"items": [
{
"id": 1,
"title": "Clean Code",
"author": "Robert C. Martin",
"status": "PLANNED",
"externalId": "OL12345M",
"comment": null
}
],
"count": 1
}
Mala respuesta – no porque "no esté en el estándar", sino porque el cliente tiene que adivinar qué es data, y por qué al lado hay totalCount en lugar de count, y por qué los elementos se llaman list en lugar de items:
{
"data": [
{ "id": 1, "title": "Clean Code" }
],
"totalCount": 1
}
¿Se puede hacer así? Se puede. Pero entonces también debe convertirse en su convención. El problema es que los principiantes suelen hacerlo así: un endpoint devuelve items + count, otro data + totalCount, un tercero directamente "solo un array", y un cuarto result. Y eso ya no es un contrato, sino una búsqueda del tesoro.
A nivel de DTO se ve muy simple:
class ReadingListResponse {
ReadingItemResponse[] items; // elementos de la lista (lo que el cliente recorrerá)
int count; // cantidad de elementos (útil para UI y paginación)
}
Observe: incluso en el código Java es útil mantener los mismos nombres que en JSON. No porque "siempre sea así", sino porque en un proyecto educativo esto reduce la cantidad de partes móviles.
5. Nombres de DTO según su rol
Los principiantes a menudo sufren porque "hay muchas clases en el proyecto, me confundo". Y es normal: los DTO realmente se multiplican. Pero la confusión casi nunca se debe a la cantidad de clases, sino a que sus nombres no indican el rol. Cuando ves una clase Data, no entiendes nada. Cuando ves CreateReadingItemRequest, al menos el cerebro entiende: "esto es una entrada para crear".
Aquí es útil mantener una convención simple: en el nombre del DTO debe verse la dirección y el escenario. La dirección la mostraremos con los sufijos Request y Response. El escenario, con un verbo o contexto: Create, Update, UpdateStatus, ReadingItem, ReadingList.
Ejemplo malo (parece "breve", pero no está claro):
class ItemDto { } // no está claro: ¿es request o response? ¿para qué escenario?
class ItemDto2 { } // "2" no explica el significado, es solo un contador
class Payload { } // ¿payload de qué? ¿de qué endpoint?
Ejemplo bueno (un poco más largo, pero más claro):
class CreateReadingItemRequest { } // entrada para crear
class UpdateReadingItemRequest { } // entrada para actualizar
class UpdateStatusRequest { } // entrada para cambiar el estado (escenario separado)
class ReadingItemResponse { } // salida: un elemento
class ReadingListResponse { } // salida: colección/lista
Note: aquí no estamos "jugando a la arquitectura limpia" ni creando diez niveles de abstracción. Simplemente hacemos que una persona, al abrir un archivo, entienda de inmediato: esto es una entrada o una salida, y para qué acción.
Y un detalle más: si dos DTO coinciden en campos, eso no significa que deban fusionarse. La coincidencia de campos es coincidencia, y el propósito es significado. Por ejemplo, CreateReadingItemRequest y UpdateReadingItemRequest pueden tener el mismo conjunto de campos, pero semánticamente son operaciones diferentes. Por lo tanto, incluso una forma idéntica no anula nombres diferentes.
6. Renombrar campos y breaking change
Renombrar un campo es uno de los cambios más engañosos en el mundo del backend. Dentro del código estás acostumbrado: "bueno, renombré una variable – el IDE lo actualizó todo, las pruebas pasaron, seguimos adelante". Pero el contrato JSON no es "dentro del proyecto". Es hacia afuera. El IDE del cliente no ve sus renombramientos. Y si el contrato ya está siendo utilizado por alguien más, renombrar es casi siempre un breaking change.
Para no convertir la lección en un curso sobre versionado de API (esa es otra historia), mantengamos una idea simple: si un campo ya está publicado en el contrato y el cliente cuenta con él, entonces "simplemente renombrarlo" no es posible sin consecuencias.
Es útil verlo con un ejemplo. Imaginemos que al principio devolvíamos la respuesta así:
{
"externalId": "OL12345M",
"title": "Clean Code"
}
Y luego alguien decidió "hacerlo bonito, como en Python" y lo cambió a:
{
"external_id": "OL12345M",
"title": "Clean Code"
}
Desde el punto de vista del servidor, todo está bien: los datos están. Pero el cliente, que buscaba externalId, ahora no lo encontrará. En el mejor de los casos, mostrará un campo vacío. En el peor, fallará con un error y el usuario dirá "su servicio está roto". Y formalmente tendrá razón: el contrato cambió.
Hay cambios que normalmente son menos dolorosos. Por ejemplo, agregar un nuevo campo opcional (que el cliente puede ignorar) suele ser más fácil que renombrar uno existente. Pero por ahora nos mantenemos en un nivel básico: los nombres de los campos son una promesa, y cambiar una promesa debe hacerse con cuidado.
7. Errores típicos al nombrar JSON
Error n.º 1: mezclar estilos de nomenclatura en el mismo proyecto.
La historia más común: parte de los DTO se escribieron en camelCase, luego alguien agregó un campo en estilo snake_case, luego en la respuesta apareció de repente STATUS en mayúsculas, "porque así se nota más". El resultado es que el cliente lee el contrato como un acertijo. La regla es simple: elegir un estilo y mantenerlo en todas partes.
Error n.º 2: varios nombres para un mismo significado (sinónimos).
Hoy llamas al identificador externo externalId, mañana catalogId, pasado mañana bookId. Cada vez parece que "bueno, se entiende". Pero en una semana tú mismo dejas de entender si son tres campos diferentes o lo mismo. El minidiccionario del proyecto te salva: un significado – un nombre.
Error n.º 3: campos "universales" como data, info, result, que no le dicen nada al cliente.
A veces parece que data es conveniente, porque "ahí están los datos". Pero el contrato de la API lo leen personas y programas. items dice: "esto son los elementos de la lista". count dice: "esto es la cantidad". data dice: "bueno… algo". Cuanto más concreto sea el nombre, menos preguntas innecesarias tendrá el cliente.
Error n.º 4: abreviaturas aleatorias y siglas extrañas.
extId, authNm, ttl se ven "compactos", pero se leen como un código cifrado. En un proyecto educativo valoramos la claridad por encima del ahorro de dos caracteres. Si el campo se usa realmente con frecuencia, igual lo escribirás muchas veces en el código, y un nombre comprensible se amortiza más rápido de lo que parece.
Error n.º 5: renombrar campos "por estética" sin entender las consecuencias.
Dentro del código Java, renombrar suele ser seguro: el IDE actualiza las referencias. El contrato JSON externo no funciona así. Si el campo ya "salió al exterior", cualquier renombramiento puede romper a los clientes. Por lo tanto, antes de cambiar un nombre, pregúntate: "¿Quién lo está usando ya y qué les pasará?".
GO TO FULL VERSION