JSON Toolkit

FAQ de trabajo en equipo: contratos, configuración y fixtures

En resumen: Estilo de nomenclatura, formatos de hora, semántica de null, forma de los enums, formatos de configuración, organización de fixtures y conflictos de fusión: doce contrapartidas sobre las que los equipos realmente discuten.

Intermedio~12 min de lecturaPublicado el 2026-10-30Actualizado el 2026-10-30
Portada de las preguntas frecuentes sobre trabajo en equipo

En resumen: zanja estas cuestiones de una vez en una convención de equipo —un envoltorio {"data":…,"meta":…} con códigos de estado HTTP, un único estilo de nomenclatura en todo el sitio, marcas de tiempo RFC 3339 con zona horaria y enums de cadenas— y luego evoluciona cada API nueva contra el mismo contrato solo añadiendo campos, nunca cambiándolos ni eliminándolos.

No hay una única respuesta correcta a estas preguntas, pero un equipo debe tener una única respuesta; de lo contrario, cada API nueva reinicia la misma discusión.

1. Contratos de API

¿Qué estructura debería devolver una API?

Tres formas habituales, cada una con un coste:

Forma Ejemplo Pros Contras
Datos en crudo [{...}, {...}] Simple No puede llevar metadatos
Sobre fijo {"code":0,"message":"ok","data":{...}} Tratamiento de errores uniforme El código de negocio duplica el estado HTTP
Sobre de recurso {"data":{...},"meta":{...},"links":{...}} Extensible, semántica clara Un nivel extra de anidamiento

Para las APIs de negocio internas, prefiere «sobre de recurso + códigos de estado HTTP que expresen el resultado»:

{
  "data": [{ "id": "1", "name": "示例" }],
  "meta": { "total": 128, "page": 2, "pageSize": 20 }
}

Para los errores, usa un código de estado HTTP más un cuerpo de error estructurado en lugar de anidar otro code:

{
  "error": {
    "type": "validation_failed",
    "message": "请求参数校验失败",
    "details": [
      { "path": "/email", "reason": "格式不正确" }
    ]
  }
}

Punto clave: los códigos de estado HTTP ya expresan éxito o fracaso, así que un código de negocio es redundante. Cuando ambos sistemas conviven, las inconsistencias del tipo «201 pero el código es 500» son casi inevitables.

Evita los arrays desnudos. Con un array en el nivel superior no puedes adjuntar paginación, totales ni cursores, y añadirlos después implica cambiar a todos los llamadores.

¿Los nombres de campo deberían usar snake_case o camelCase?

Elige uno, aplícalo en todo el sitio y no los mezcles nunca dentro de un mismo documento JSON.

Estilo Ecosistema habitual Ejemplo
camelCase JavaScript / TypeScript / Java userName, createdAt
snake_case Python / Ruby / Go (en parte) / columnas de base de datos user_name, created_at
kebab-case Poco común; las claves con guion son incómodas de acceder user-name

Criterios:

  1. Quién consume este JSON: elige camelCase cuando domine el frontend, snake_case cuando dominen los pipelines de datos y los servicios en Python;
  2. ¿Se mapea directamente a columnas de base de datos?: snake_case ahorra una capa de conversión si pasas las estructuras de tabla tal cual;
  3. Continuidad de los sistemas existentes: mantén lo que use la API existente; la consistencia pesa más que la corrección.

Si de verdad tienes que convertir, hazlo en la frontera (gateway o capa de peticiones del frontend) en lugar de dispersar llamadas toCamelCase por el código de negocio.

¿Qué formato deberían usar los campos de hora?

Usa cadenas RFC 3339 / ISO 8601 con un desfase de zona horaria:

{
  "createdAt": "2026-10-30T11:37:57+08:00",
  "updatedAt": "2026-10-30T03:37:57Z"
}

Puntos clave:

  • Incluye la zona horaria: 2026-10-30 11:37:57 sin zona acabará rompiendo en escenarios de varias zonas horarias;
  • Usa Z o un desfase de forma consistente: ambos son correctos, pero no los mezcles dentro de una misma API;
  • Evita las marcas de tiempo numéricas: si son segundos o milisegundos, y si incluyen segundos intercalares, cada equipo tiene que reinterpretarlo. Cuando se requiera compacidad, usa un entero de milisegundos, pero fija la unidad en la documentación.

Prefiere nombres de campo con sufijo At, como createdAt / updatedAt / deletedAt, para que se vea a simple vista que el valor es un punto en el tiempo y no una duración. Para duraciones usa timeoutMs, ttlSeconds: pon la unidad en el nombre.

¿Los nulos deberían ser null o un campo omitido?

Depende de si «este campo tiene sentido»:

Semántica Forma Notas
El campo existe pero aún no tiene valor "name": null Dice explícitamente «se comprobó y está vacío»
El campo es irrelevante para este recurso Omitir Por ejemplo, un usuario normal no debería tener vipExpireAt
Actualización parcial (PATCH) Omitir = no cambiar Solo null significa «borrar» aquí

El tercer caso es el que más se pasa por alto. En una petición PATCH:

{ "nickname": null }

esto significa «borra el apodo», mientras que dejar nickname fuera del cuerpo de la petición significa «no toques el apodo». Si tratas ambos como «borrar», un usuario que cambie su correo borrará su apodo.

Una vez más: no uses undefined. No es un valor JSON válido, y JSON.stringify elimina la clave por completo, así que el comportamiento coincide con «omitir» mientras el código parece decir «se pasó un valor vacío explícitamente».

¿Cómo deberían nombrarse los campos booleanos?

Usa los prefijos is / has / can / should y evita las negaciones.

{
  "isActive": true,
  "hasPassword": false,
  "canEdit": true
}

Tres lecciones:

  1. Sin negaciones: isDisabled: false obliga al lector a invertirlo una vez; isEnabled: true es más directo. La doble negación (isNotDisabled) es un desastre;
  2. No uses números para los booleanos: "enabled": 1 obliga a cada consumidor a escribir === 1 una vez;
  3. No uses cadenas: "enable": "true" es truthy en JavaScript, y if (data.enable) también es verdadero para "false"; ese tipo de errores cuesta muchísimo rastrearlos.

¿Los campos enum deberían usar números o cadenas?

Usa cadenas a menos que haya una restricción clara de almacenamiento o ancho de banda.

{ "status": "active" }

Comparado con { "status": 1 }, las cadenas ganan porque:

  • Los logs, las capturas de paquetes y las bases de datos son directamente legibles sin consultar una tabla de mapeo;
  • Añadir un valor al enum no cambia el significado de los valores existentes (insertar un valor en medio de un enum numérico desplaza todo);
  • El frontend no necesita mantener sincronizado un mapeo 1 → 'active'.

El coste son unos pocos bytes extra, casi despreciables tras la compresión.

Práctica complementaria: usa minúsculas + guiones bajos para los valores de enum de cadena (pending_review), y restríngelos con enum en el Schema para que los valores inválidos se rechacen en la fase de validación.

2. Configuración y fixtures

¿La configuración debería ser JSON, YAML o TOML?

Elige por propósito, no por preferencia:

Formato Bueno para Malo para
JSON Lectura y escritura programática, datos estrictamente estructurados, validación con Schema Edición manual frecuente (no se admiten comentarios)
YAML Configuración de despliegue muy anidada (K8s) La indentación escrita a mano es propensa a errores; yes/no se convierten en booleanos
TOML Configuración plana de dependencias y herramientas (Cargo.toml, pyproject.toml) Mala legibilidad con anidamiento profundo
JSONC Configuración de editor que necesita comentarios (tsconfig.json) Cruzar fronteras entre sistemas

Un error habitual en los equipos es guardar configuración de negocio en YAML: la conversión implícita de tipos de YAML (1.0 se convierte en número, NO se convierte en booleano, 2026-10-30 se convierte en fecha) produce resultados distintos entre analizadores.

Si la configuración debe validarla estrictamente un programa, elige JSON + Schema, y documenta los comentarios mediante un campo _comment o documentación aparte.

¿Cómo deberían organizarse los fixtures de prueba?

Organízalos por «una base + diferencias mínimas», no copiando un JSON enorme para cada caso de prueba.

fixtures/
├─ users/
│  ├─ base.json              # Objeto de usuario base
│  ├─ no-email.json          # Solo falta email
│  └─ invalid-age.json       # Solo age está fuera de rango
└─ fixtures.ts               # Funciones de utilidad para cargar y fusionar superficialmente
import baseUser from './users/base.json';

export function makeUser(overrides: Partial<User> = {}): User {
  return { ...baseUser, ...overrides };
}

Así, un caso de prueba nuevo solo necesita escribir los campos que difieren, y cambiar un campo de la base actualiza todos los casos automáticamente.

Tres reglas:

  1. Los fixtures deben pasar la validación del Schema: el propio fixture base debería ser dato válido;
  2. No exportes datos reales de producción: teléfonos, números de identificación y direcciones son todos información personal;
  3. Los fixtures van al control de versiones pero deben seguir siendo pequeños: un solo archivo de más de unas decenas de KB merece poda o generación.

¿Cómo reduzco los conflictos de fusión en archivos JSON?

JSON entra en conflicto con facilidad: un formateador reordena todo el archivo de golpe, así que dos personas que cambian una línea cada una entran en conflicto en el archivo completo.

Cuatro tácticas:

  1. Unificar el formato y añadirlo a .editorconfig: indentación, finales de línea y salto de línea final consistentes evitan diffs irrelevantes;
  2. Ordenar las claves: acuerda el orden alfabético para que un campo nuevo no choque con el punto de inserción de otro;
  3. Dividir archivos: parte un config.json gigante en varios archivos por dominio, y la superficie de conflicto se reduce de forma natural;
  4. Evitar commits de «reformatear todo»: separa el formateo de los cambios de negocio en dos commits para que la revisión siga siendo legible.

Si el equipo usa archivos de bloqueo (pnpm-lock.yaml, package-lock.json), no los fusiones a mano; regenéralos:

git checkout --theirs pnpm-lock.yaml
pnpm install

¿Cómo mantengo la compatibilidad de versiones?

El principio es «añadir, no cambiar, no eliminar».

Cambio Compatibilidad Enfoque
Añadir un campo opcional Compatible Añádelo; los consumidores ignoran los campos desconocidos
Añadir un campo obligatorio Incompatible Sube la versión, o proporciona un valor por defecto
Eliminar un campo Incompatible Márcalo primero como obsoleto, vigila los llamadores y luego elimínalo
Cambiar el tipo de un campo Incompatible Añade un nombre de campo nuevo y escribe en ambos durante la transición
Cambiar el conjunto de valores del enum Depende Añadir valores es compatible; eliminarlos no

Mecanismos de apoyo:

  • Los consumidores usan «análisis tolerante»: ignoran los campos desconocidos y aportan valores por defecto para los que faltan;
  • El servidor conserva los campos antiguos durante un tiempo y devuelve una cabecera Deprecation en la respuesta como aviso;
  • En el Schema, no marques los campos nuevos como required, y etiqueta los obsoletos con deprecated: true.

¿Cómo valido los contratos en CI?

Trata el Schema como parte del código y valídalo en cada commit.

# Validar datos de ejemplo contra el Schema (usando ajv-cli)
npx ajv-cli validate -s schemas/user.json -d "fixtures/users/*.json" --strict=false

Ejecuta tres cosas en CI:

  1. Validez del propio Schema: si la declaración $schema es válida y si $ref se resuelve;
  2. Si los fixtures coinciden con el Schema: asegura que los datos de prueba no se desvían;
  3. Si el contrato es compatible hacia atrás: compáralo con la versión anterior y busca campos eliminados o tipos cambiados.

El tercer punto necesita soporte de herramientas (como openapi-diff o json-schema-diff); sin él, al menos compara con un script los cambios en los arrays required y en los conjuntos de claves de properties.

3. Referencia rápida

Pregunta Recomendación
Estructura de respuesta {"data":..., "meta":...}, con códigos de estado HTTP para los errores
Estilo de nomenclatura camelCase (frontend primero) o snake_case (datos primero), consistente en todo el sitio
Formato de hora Cadenas RFC 3339 con zona horaria, nombres de campo terminados en At
Nulos Usa null para «sin valor», omite los campos irrelevantes, la omisión en PATCH significa «no cambiar»
Nomenclatura de booleanos Prefijos is/has/can, sin negaciones, sin 0/1
Enums Cadenas, minúsculas con guiones bajos, añade enum en el Schema
Formato de configuración JSON para validación programática, TOML o JSONC para edición manual
Fixtures Una base + anulaciones de diferencia mínima, válidas contra el Schema
Conflictos de fusión Unificar formato, ordenar claves, dividir archivos
Compatibilidad de versiones Añadir, no cambiar, no borrar; marcar como obsoleto antes de eliminar