JSON Toolkit

Gerenciando JSON para configuração, fixtures e dados locais

Resumo: A configuração precisa ser em camadas e validada; as fixtures precisam ser determinísticas, legíveis e comparáveis por diff. Se esses dois tipos de arquivo forem mal administrados, o custo de depuração fica alto por muito tempo.

Iniciante~10 min de leituraPublicado em 2026-10-30Atualizado em 2026-10-30
Capa de Gerenciando JSON para configuração, fixtures e dados locais

Resumo da ópera: construa a configuração como padrões, ambiente e sobrescritas locais, com validação na inicialização antes de se preocupar com o formato do arquivo; torne as fixtures determinísticas em tempo, IDs, ordem de chaves e indentação para que permaneçam comparáveis por diff, e só então automatize o mascaramento.

Esses dois tipos de JSON não participam da lógica de negócio, mas determinam "quanto tempo leva para um novo colega começar a rodar" e "quanto tempo leva para localizar um problema de produção".

1. Configuração: uma estrutura em três camadas

Camada Origem Mantido por Commitado
Padrões Constantes no código / default.json Desenvolvedores Sim
Configuração de ambiente config/production.json, variáveis de ambiente Operações Sim (sem segredos)
Sobrescritas locais .env.local, config/local.json Indivíduos Não (ignorado pelo git)

Ordem de merge: padrões → ambiente → local, com cada camada posterior sobrescrevendo a anterior. Mescle apenas tão profundamente quanto o negócio precisar; não faça merge profundo cegamente — o merge profundo torna problemas como "arrays foram concatenados inesperadamente" extremamente difíceis de enxergar.

/**
 * Mescla camadas de configuração por prioridade; camadas posteriores sobrescrevem as anteriores, e arrays são substituídos por inteiro em vez de concatenados.
 * @param layers Camadas de configuração da menor para a maior prioridade
 * @returns O objeto de configuração mesclado
 */
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;
      }

      // Arrays e escalares são sempre substituídos por inteiro para evitar concatenação acidental de elementos
      mergedConfig[configKey] = configValue;
    }
    return mergedConfig;
  }, {});
}

2. Valide na inicialização, não espere até o tempo de execução

O custo de um erro de configuração cresce exponencialmente com o quão tarde ele é descoberto:

Quando é descoberto Custo
Na inicialização Uma linha de log, reinicie e siga em frente
Na primeira requisição Alguns recursos dão erro, uma hora de depuração
Quando um job agendado dispara Alertas no meio da noite
No dia de fechamento Um incidente de conciliação
import Ajv2020 from 'ajv/dist/2020';
import configSchema from './config.schema.json' with { type: 'json' };

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

/**
 * Valida a configuração e lança imediatamente em caso de falha, implementando fail fast.
 * @param rawConfig A configuração mesclada crua
 * @returns A configuração 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>;
}

Princípios de apoio:

  • Saia imediatamente quando um item obrigatório estiver ausente; não forneça um padrão "que parece plausível" (como padronizar o endereço do banco de dados para localhost);
  • Saia imediatamente em erros de tipo; a string "false" não é o booleano false, então variáveis de ambiente precisam ser convertidas explicitamente após a leitura;
  • Segredos nunca entram no repositório; leia-os apenas de variáveis de ambiente ou de um serviço de gerenciamento de segredos.

3. Que formato usar para arquivos de configuração

Arquivo Formato Motivo
package.json JSON padrão O gerenciador de pacotes só aceita isso
tsconfig.json JSONC Comentários permitidos, oficialmente suportado
.vscode/settings.json JSONC O mesmo que acima
Configuração de negócio YAML ou JSONC Precisa de comentários explicando o que cada item significa
Lista de variáveis de ambiente .env Pares chave-valor planos, fáceis de injetar

Um teste prático: este arquivo será lido por ferramentas não humanas? Se sim → JSON padrão; se só o seu próprio código o lê → um dialeto serve.

4. Fixtures de teste: diretórios e nomeação

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

Uma boa convenção de nomeação é objeto-cenário: order-missing-field é bem mais útil que test1.json. Nomes de arquivo não-ASCII ocasionalmente causam problemas de codificação em cadeias de ferramentas multiplataforma, então mantenha os nomes de arquivo em inglês enquanto o conteúdo pode permanecer em chinês.

4.1 Três tipos de caso, três tipos de expectativa

Diretório Expectativa Finalidade
valid/ A validação passa Fluxo principal, geração de tipos
invalid/ A validação falha, e o erro aponta para o campo esperado Tratamento de erro, texto de mensagem
edge/ O comportamento é explícito (passar ou falhar tudo bem, mas precisa ser asseverado) Proteção contra regressões

O JSONTestSuite usa os prefixos y_ / n_ / i_ para os mesmos três tipos (deve aceitar / deve rejeitar / definido pela implementação), e você pode tomar emprestada essa convenção de nomeação diretamente.

5. Determinismo: torne as fixtures comparáveis por diff

Fixtures instáveis tornam a revisão de código inútil:

Fonte de ruído Como lidar
Hora atual Fixe-a em uma constante, por exemplo 2026-01-01T00:00:00Z
IDs aleatórios / UUIDs Use uma sequência incremental ou valores fixos
Ordem das chaves Ordene alfabeticamente no momento da geração
Formatação de float Mantenha uniformemente duas casas decimais ou converta para strings
Fuso horário Use UTC em toda parte
Indentação Fixa em 2 espaços, verificada pelo Prettier no CI
/**
 * Ordena chaves recursivamente para produzir texto JSON estável e comparável por diff.
 * @param value O valor a serializar
 * @param indentWidth Largura da indentação
 * @returns Uma string JSON com ordem estável
 */
function toStableJson(value: unknown, indentWidth = 2): string {
  /**
   * Ordena recursivamente as chaves de objeto.
   * @param input Qualquer valor JSON
   * @returns Um novo valor com chaves 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. Mascaramento e conformidade

Dados reais não podem ser usados diretamente como fixtures:

  • Nomes → placeholders como "John Doe" e "Jane Roe";
  • Números de telefone → números obviamente fictícios como 13800138000;
  • Documentos de identidade → números falsos com o formato correto mas com dígito verificador inválido;
  • Endereços → mantenha a estrutura de província/cidade, descarte o número do imóvel;
  • Valores → mantenha a ordem de magnitude, mas nunca mantenha informações reais de conta.

É melhor que a equipe tenha um scripts/mask-fixture.mjs que rode sobre uma amostra de produção exportada para gerar uma versão mascarada, evitando "mudar três lugares à mão e esquecer o resto".

7. Migração e versões

Fixtures também envelhecem. Adicione um campo schemaVersion à fixture, ou coloque um arquivo VERSION no diretório:

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

Quando o contrato de API é atualizado:

  1. Adicione um novo diretório de fixtures (v3/); não modifique fixtures antigas no lugar;
  2. Mantenha as fixtures antigas para regressão até que a versão antiga da API seja aposentada;
  3. Rode as duas versões no CI para confirmar a compatibilidade retroativa.

8. Lista de verificação

  • [ ] A estrutura de configuração em três camadas está clara, e sobrescritas locais não são commitadas;
  • [ ] A validação acontece na inicialização, e a falha sai imediatamente;
  • [ ] Segredos vêm apenas de variáveis de ambiente; arquivos de configuração contêm apenas placeholders;
  • [ ] As fixtures são divididas em diretórios valid/invalid/edge, e os nomes expressam o cenário;
  • [ ] As fixtures são determinísticas (tempo, IDs e ordem fixos) e comparáveis por diff;
  • [ ] Dados reais foram processados pelo script de mascaramento;
  • [ ] Atualizações de contrato adicionam um novo diretório de versão em vez de editar no lugar.

Perguntas frequentes

Como as camadas de configuração devem ser ordenadas e mescladas?

Use três camadas — padrões, configuração de ambiente e sobrescritas locais — mesclando padrões → ambiente → local, com cada camada posterior sobrescrevendo a anterior. Mescle apenas tão profundamente quanto o negócio precisa e substitua arrays e escalares por inteiro, para que o merge profundo não concatene arrays inesperadamente. Ignore no git o arquivo de sobrescrita local em vez de commitar configurações pessoais.

Por que validar a configuração na inicialização?

O custo de um erro de configuração cresce exponencialmente com o quão tarde ele é descoberto: uma linha de log na inicialização versus um incidente de conciliação no dia de fechamento. Valide contra um JSON Schema na inicialização e saia imediatamente em itens obrigatórios ausentes ou erros de tipo, em vez de fornecer um padrão "que parece plausível".

Devo usar dados de produção diretamente como fixtures de teste?

Não. Nomes, números de telefone, documentos de identidade, endereços e valores todos precisam de placeholders ou valores obviamente fictícios, e a equipe deve ter um scripts/mask-fixture.mjs que rode sobre uma exportação de produção para gerar uma versão mascarada, evitando edições manuais que esqueçam algo.