JSON Toolkit

Gestión de JSON para configuración, fixtures y datos locales

En resumen: La configuración debe estar por capas y validada; los fixtures deben ser deterministas, legibles y comparables con diff. Si estos dos tipos de archivos se gestionan mal, el coste de diagnóstico se mantiene alto durante mucho tiempo.

Principiante~10 min de lecturaPublicado el 2026-10-30Actualizado el 2026-10-30
Portada de Gestión de JSON para configuración, fixtures y datos locales

Conclusión: construye la configuración como valores por defecto, entorno y overrides locales, con validación al arranque, antes de preocuparte por el formato de archivo; haz que los fixtures sean deterministas en tiempo, IDs, orden de claves y sangría para que sigan siendo comparables con diff, y solo entonces automatiza el enmascarado.

Estos dos tipos de JSON no participan en la lógica de negocio, y aun así determinan "cuánto tarda un compañero nuevo en ponerse en marcha" y "cuánto tarda localizar un problema en producción".

1. Configuración: una estructura de tres capas

Capa Origen Mantenida por Versionada
Valores por defecto Constantes en el código / default.json Desarrolladores Sí
Configuración de entorno config/production.json, variables de entorno Operaciones Sí (sin secretos)
Overrides locales .env.local, config/local.json Individuos No (ignorado por git)

Orden de fusión: valores por defecto → entorno → local, con cada capa posterior sobrescribiendo la anterior. Fusiona solo tan profundo como lo necesite el negocio; no hagas fusiones profundas a ciegas: la fusión profunda hace que problemas como "los arrays se concatenaron inesperadamente" sean extremadamente difíciles de detectar.

/**
 * Fusiona capas de configuración por prioridad; las capas posteriores sobrescriben a las anteriores, y los arrays se reemplazan por completo en lugar de concatenarse.
 * @param layers Capas de configuración de menor a mayor prioridad
 * @returns El objeto de configuración fusionado
 */
function mergeConfigLayers(layers: Array<Record<string, unknown>>): Record<string, unknown> {
  return layers.reduce<Record<string, unknown>>((mergedConfig, currentLayer) => {
    for (const [configKey, configValue] of Object.entries(currentLayer)) {
      const isPlainObject =
        typeof configValue === 'object' && configValue !== null && !Array.isArray(configValue);
      const existingValue = mergedConfig[configKey];

      if (isPlainObject && typeof existingValue === 'object' && existingValue !== null) {
        mergedConfig[configKey] = mergeConfigLayers([
          existingValue as Record<string, unknown>,
          configValue as Record<string, unknown>,
        ]);
        continue;
      }

      // Los arrays y los escalares siempre se reemplazan por completo para evitar concatenaciones accidentales de elementos
      mergedConfig[configKey] = configValue;
    }
    return mergedConfig;
  }, {});
}

2. Valida al arranque, no esperes al tiempo de ejecución

El coste de un error de configuración crece exponencialmente con lo tarde que se descubre:

Cuándo se descubre Coste
Al arranque Una línea de log, reinicias y sigues
En la primera petición Algunas funcionalidades fallan, una hora de depuración
Cuando se dispara un trabajo programado Alertas en mitad de la noche
El día de la liquidación Un incidente de conciliación
import Ajv2020 from 'ajv/dist/2020';
import configSchema from './config.schema.json' with { type: 'json' };

const ajv = new Ajv2020({ allErrors: true, strict: true });

/**
 * Valida la configuración y lanza de inmediato si falla, implementando fail fast.
 * @param rawConfig La configuración fusionada en bruto
 * @returns La configuración validada
 */
export function assertConfig(rawConfig: unknown): Record<string, unknown> {
  const validate = ajv.compile(configSchema);
  if (!validate(rawConfig)) {
    const messages = (validate.errors ?? []).map(
      (errorItem) => `${errorItem.instancePath || '/'} ${errorItem.message ?? ''}`,
    );
    throw new Error(`配置校验失败:\n${messages.join('\n')}`);
  }
  return rawConfig as Record<string, unknown>;
}

Principios de apoyo:

  • Sal inmediatamente cuando falte un elemento obligatorio; no proporciones un valor por defecto "que parece plausible" (como poner localhost como dirección de base de datos por defecto);
  • Sal inmediatamente ante errores de tipo; la cadena "false" no es el booleano false, así que las variables de entorno deben convertirse explícitamente después de leerse;
  • Los secretos nunca entran en el repositorio; léelos solo desde variables de entorno o desde un servicio de gestión de secretos.

3. Qué formato usar para los archivos de configuración

Archivo Formato Motivo
package.json JSON estándar El gestor de paquetes solo acepta esto
tsconfig.json JSONC Comentarios permitidos, soportado oficialmente
.vscode/settings.json JSONC Igual que el anterior
Configuración de negocio YAML o JSONC Necesita comentarios que expliquen qué significa cada elemento
Lista de variables de entorno .env Pares clave-valor planos, fáciles de inyectar

Una prueba práctica: ¿este archivo lo leerá alguna herramienta que no sea humana? Si sí → JSON estándar; si solo lo lee tu propio código → un dialecto vale.

4. Fixtures de prueba: directorios y nomenclatura

tests/fixtures/
├─ valid/
│  ├─ order-minimal.json
│  ├─ order-full.json
│  └─ order-with-null-fields.json
├─ invalid/
│  ├─ order-missing-id.json
│  ├─ order-price-as-number.json
│  └─ order-nested-broken.json
└─ edge/
   ├─ big-integer.json
   ├─ emoji-and-surrogate.json
   └─ deep-nesting-64.json

Una buena convención de nomenclatura es objeto-escenario: order-missing-field es mucho más útil que test1.json. Los nombres de archivo no ASCII a veces causan problemas de codificación en toolchains multiplataforma, así que mantén los nombres de archivo en inglés mientras el contenido puede seguir en chino.

4.1 Tres tipos de casos, tres tipos de expectativas

Directorio Expectativa Propósito
valid/ La validación pasa Flujo principal, generación de tipos
invalid/ La validación falla, y el error apunta al campo esperado Manejo de errores, redacción de mensajes
edge/ El comportamiento está explícito (pasar o fallar da igual, pero debe verificarse) Protección contra regresiones

JSONTestSuite usa los prefijos y_ / n_ / i_ para los mismos tres tipos (debería aceptar / debería rechazar / definido por la implementación), y puedes tomar prestada esa convención de nomenclatura directamente.

5. Determinismo: haz que los fixtures sean comparables con diff

Los fixtures inestables inutilizan la revisión de código:

Fuente de ruido Cómo gestionarla
Hora actual Fíjala a una constante, p. ej. 2026-01-01T00:00:00Z
IDs aleatorios / UUIDs Usa una secuencia incremental o valores fijos
Orden de claves Ordena alfabéticamente en el momento de la generación
Formato de decimales Mantén uniformemente dos decimales o convierte a cadenas
Zona horaria Usa UTC en todas partes
Sangría Fija en 2 espacios, comprobada por Prettier en CI
/**
 * Ordena las claves de forma recursiva para producir texto JSON estable y comparable con diff.
 * @param value El valor que se va a serializar
 * @param indentWidth Ancho de la sangría
 * @returns Una cadena JSON con orden estable
 */
function toStableJson(value: unknown, indentWidth = 2): string {
  /**
   * Ordena las claves de los objetos de forma recursiva.
   * @param input Cualquier valor JSON
   * @returns Un valor nuevo con las claves ordenadas
   */
  const sortKeysDeep = (input: unknown): unknown => {
    if (Array.isArray(input)) {
      return input.map((item) => sortKeysDeep(item));
    }
    if (typeof input === 'object' && input !== null) {
      return Object.fromEntries(
        Object.keys(input)
          .sort()
          .map((entryKey) => [entryKey, sortKeysDeep((input as Record<string, unknown>)[entryKey])]),
      );
    }
    return input;
  };

  return JSON.stringify(sortKeysDeep(value), null, indentWidth);
}

6. Enmascarado y cumplimiento normativo

Los datos reales no pueden usarse directamente como fixtures:

  • Nombres → marcadores de posición como "John Doe" y "Jane Roe";
  • Números de teléfono → números obviamente ficticios como 13800138000;
  • Números de documento de identidad → números falsos con el formato correcto pero un dígito de control inválido;
  • Direcciones → mantener la estructura provincia/ciudad, quitar el número de la casa;
  • Importes → mantener el orden de magnitud, pero nunca mantener información real de cuentas.

Lo mejor es que el equipo tenga un scripts/mask-fixture.mjs que se ejecute sobre una muestra de producción exportada para generar una versión enmascarada, evitando "cambiar tres sitios a mano y olvidar el resto".

7. Migración y versiones

Los fixtures también se quedan obsoletos. Añade un campo schemaVersion al fixture, o coloca un archivo VERSION en el directorio:

{
  "schemaVersion": 3,
  "id": "1001",
  "amount": "89.50"
}

Cuando se actualiza el contrato de API:

  1. Añade un directorio de fixtures nuevo (v3/); no modifiques los fixtures antiguos in situ;
  2. Mantén los fixtures antiguos para regresión hasta que se retrase la versión antigua de la API;
  3. Ejecuta ambas versiones en CI para confirmar la compatibilidad hacia atrás.

8. Lista de comprobación

  • [ ] La estructura de configuración en tres capas es clara, y los overrides locales no se versionan;
  • [ ] La validación ocurre al arranque, y el fallo termina de inmediato;
  • [ ] Los secretos solo vienen de variables de entorno; los archivos de configuración contienen únicamente marcadores de posición;
  • [ ] Los fixtures están divididos en directorios valid/invalid/edge, y los nombres expresan el escenario;
  • [ ] Los fixtures son deterministas (tiempo, IDs y orden fijos) y comparables con diff;
  • [ ] Los datos reales han pasado por el script de enmascarado;
  • [ ] Las actualizaciones de contrato añaden un directorio de versión nuevo en lugar de editar in situ.

FAQ

¿Cómo deben ordenarse y fusionarse las capas de configuración?

Usa tres capas — valores por defecto, configuración de entorno y overrides locales — fusionando valores por defecto → entorno → local, con cada capa posterior sobrescribiendo a la anterior. Fusiona solo tan profundo como lo necesite el negocio y reemplaza arrays y escalares por completo, para que la fusión profunda no pueda concatenar arrays inesperadamente. Ignora con git el archivo de override local en lugar de versionar ajustes personales.

¿Por qué validar la configuración al arranque?

El coste de un error de configuración crece exponencialmente con lo tarde que se descubre: una línea de log al arranque frente a un incidente de conciliación el día de la liquidación. Valida contra un JSON Schema al arranque y sal de inmediato ante elementos obligatorios ausentes o errores de tipo, en lugar de proporcionar un valor por defecto "que parece plausible".

¿Debo usar datos de producción directamente como fixtures de prueba?

No. Nombres, números de teléfono, números de documento de identidad, direcciones e importes necesitan todos marcadores de posición o valores obviamente ficticios, y el equipo debe tener un scripts/mask-fixture.mjs que se ejecute sobre una exportación de producción para generar una versión enmascarada, evitando ediciones manuales que se dejan algo fuera.