JSON Toolkit

FAQ de colaboração em equipe: contratos, configuração e fixtures

Resumo: Estilo de nomeação, formatos de hora, semântica de null, formas de enum, formatos de configuração, organização de fixtures e conflitos de merge — doze trade-offs sobre os quais as equipes realmente discutem.

Intermediário~12 min de leituraPublicado em 2026-10-30Atualizado em 2026-10-30
Capa da FAQ de colaboração em equipe

Resumo da ópera: resolva estas questões uma vez em uma convenção de equipe — um envelope {"data":…,"meta":…} com códigos de status HTTP, um único estilo de nomeação em todo o site, timestamps RFC 3339 com fuso horário e enums de string — depois evolua cada API nova contra o mesmo contrato apenas adicionando campos, nunca alterando ou excluindo.

Não há uma única resposta correta para essas perguntas, mas a equipe precisa ter uma única resposta — caso contrário toda API nova reinicia a mesma discussão.

1. Contratos de API

Que estrutura uma API deve retornar?

Três formatos comuns, cada um com um custo:

Formato Exemplo Prós Contras
Dados crus [{...}, {...}] Simples Não consegue carregar metadados
Envelope fixo {"code":0,"message":"ok","data":{...}} Tratamento de erro uniforme O código de negócio duplica o status HTTP
Envelope de recurso {"data":{...},"meta":{...},"links":{...}} Extensível, semântica clara Um nível extra de aninhamento

Para APIs de negócio internas, prefira "envelope de recurso + códigos de status HTTP que expressam o resultado":

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

Para erros, use um código de status HTTP mais um corpo de erro estruturado em vez de aninhar outro code:

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

Ponto-chave: os códigos de status HTTP já expressam sucesso ou falha, então um código de negócio é redundante. Quando os dois sistemas coexistem, inconsistências como "201 mas o code é 500" são quase inevitáveis.

Evite arrays crus. Com um array no nível superior você não consegue anexar paginação, totais ou cursores, e adicioná-los depois significa mudar todo chamador.

Nomes de campo devem usar snake_case ou camelCase?

Escolha um, aplique em todo o site e nunca os misture dentro de um mesmo documento JSON.

Estilo Ecossistema comum Exemplo
camelCase JavaScript / TypeScript / Java userName, createdAt
snake_case Python / Ruby / Go (em parte) / colunas de banco de dados user_name, created_at
kebab-case Raro; chaves com hífen são desconfortáveis de acessar user-name

Critérios:

  1. Quem consome este JSON: escolha camelCase quando o frontend domina, snake_case quando pipelines de dados e serviços Python dominam;
  2. Ele mapeia diretamente para colunas de banco de dados: snake_case economiza uma camada de conversão ao repassar estruturas de tabela diretamente;
  3. Continuidade dos sistemas existentes: mantenha o que a API existente usa; consistência vence correção.

Se você realmente precisa converter, faça na fronteira (gateway ou camada de requisição do frontend) em vez de espalhar chamadas toCamelCase pelo código de negócio.

Que formato os campos de hora devem usar?

Use strings RFC 3339 / ISO 8601 com um offset de fuso horário:

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

Pontos-chave:

  • Inclua o fuso horário: 2026-10-30 11:37:57 sem fuso vai inevitavelmente quebrar em cenários entre fusos;
  • Use Z ou um offset de forma consistente: ambos são corretos, mas não os misture dentro de uma API;
  • Evite timestamps numéricos: se são segundos ou milissegundos, e se segundos bissextos estão incluídos, toda equipe precisa reinterpretar. Quando a compacidade for necessária, use um inteiro em milissegundos, mas fixe a unidade na documentação.

Prefira nomes de campo com sufixo At, como createdAt / updatedAt / deletedAt, para que fique óbvio à primeira vista que o valor é um ponto no tempo e não uma duração. Para durações use timeoutMs, ttlSeconds — coloque a unidade no nome.

Nulos devem ser null ou um campo omitido?

Depende se "este campo é significativo":

Semântica Forma Observações
O campo existe mas ainda não tem valor "name": null Diz explicitamente "foi verificado e está vazio"
O campo é irrelevante para este recurso Omitir Por exemplo, um usuário comum não deve ter vipExpireAt
Atualização parcial (PATCH) Omitir = não mudar Só null significa "limpar" aqui

O terceiro caso é o mais comumente negligenciado. Em uma requisição PATCH:

{ "nickname": null }

isso significa "limpar o apelido", enquanto omitir nickname do corpo da requisição significa "não tocar no apelido". Se você trata os dois como "limpar", um usuário que muda o e-mail apagará o apelido.

Mais uma vez: não use undefined. Ele não é um valor JSON válido, e JSON.stringify descarta a chave por completo, então o comportamento corresponde a "omitir" enquanto o código parece "um valor vazio foi passado explicitamente".

Como campos booleanos devem ser nomeados?

Use os prefixos is / has / can / should e evite negações.

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

Três lições:

  1. Sem negações: isDisabled: false força o leitor a inverter uma vez; isEnabled: true é mais direto. Dupla negação (isNotDisabled) é um desastre;
  2. Não use números para booleanos: "enabled": 1 faz todo consumidor escrever === 1 uma vez;
  3. Não use strings: "enable": "true" é verdadeiro em JavaScript, e if (data.enable) também é verdadeiro para "false"; esses bugs são extremamente difíceis de rastrear.

Campos de enum devem usar números ou strings?

Use strings, a menos que haja uma restrição clara de armazenamento ou largura de banda.

{ "status": "active" }

Comparado com { "status": 1 }, strings vencem porque:

  • Logs, capturas de pacotes e bancos de dados ficam diretamente legíveis sem consultar uma tabela de mapeamento;
  • Adicionar um valor de enum não muda o significado dos valores existentes (inserir um valor no meio de um enum numérico desloca tudo);
  • O frontend não precisa manter um mapeamento 1 → 'active' em sincronia.

O custo são alguns bytes extras, quase desprezíveis após a compressão.

Prática complementar: use minúsculas mais underscores para valores de enum de string (pending_review), e os restrinja com enum no Schema para que valores inválidos sejam rejeitados na etapa de validação.

2. Configuração e fixtures

Configuração deve ser JSON, YAML ou TOML?

Escolha por finalidade, não por preferência:

Formato Bom para Ruim para
JSON Leitura e gravação programática, dados estritamente estruturados, validação por Schema Edição manual frequente (comentários não são permitidos)
YAML Configuração de implantação profundamente aninhada (K8s) Indentação escrita à mão é propensa a erro; yes/no viram booleanos
TOML Configuração plana de dependências e ferramentas (Cargo.toml, pyproject.toml) Legibilidade ruim para aninhamento profundo
JSONC Configuração de editor que precisa de comentários (tsconfig.json) Cruzar fronteiras de sistema

Um erro comum nas equipes é armazenar configuração de negócio em YAML — a conversão implícita de tipos do YAML (1.0 vira número, NO vira booleano, 2026-10-30 vira data) produz resultados diferentes entre analisadores.

Se a configuração precisa ser estritamente validada por um programa, escolha JSON + Schema, e documente comentários por um campo _comment ou por docs separados.

Como fixtures de teste devem ser organizadas?

Organize por "uma base + diffs mínimos", não copiando um JSON enorme para cada caso de teste.

fixtures/
├─ users/
│  ├─ base.json              # Objeto de usuário base
│  ├─ no-email.json          # Apenas o e-mail está ausente
│  └─ invalid-age.json       # Apenas a idade está fora da faixa
└─ fixtures.ts               # Funções utilitárias para carregar e mesclar superficialmente
import baseUser from './users/base.json';

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

Assim um novo caso de teste só precisa escrever os campos diferentes, e mudar um campo base atualiza todos os casos automaticamente.

Três regras:

  1. As fixtures precisam passar na validação do Schema: a fixture base em si deve ser um dado válido;
  2. Não exporte dados reais de produção: números de telefone, documentos de identidade e endereços são todos informações pessoais;
  3. As fixtures pertencem ao controle de versão mas precisam permanecer pequenas: um único arquivo além de algumas dezenas de KB merece poda ou geração.

Como reduzo conflitos de merge em arquivos JSON?

JSON conflita facilmente: um formatador reflui o arquivo inteiro de uma vez, então duas pessoas mudando uma linha cada conflitam no arquivo todo.

Quatro táticas:

  1. Unifique a formatação e adicione-a ao .editorconfig: indentação, fins de linha e nova linha final consistentes evitam diffs não relacionados;
  2. Ordene as chaves: acorde uma ordenação alfabética para que um campo novo não colida com o ponto de inserção de outra pessoa;
  3. Divida arquivos: quebre um config.json gigante em vários arquivos por domínio, e a superfície de conflito encolhe naturalmente;
  4. Evite commits de "reformatar tudo": separe a formatação das mudanças de negócio em dois commits para que a revisão permaneça legível.

Se a equipe usa arquivos de lock (pnpm-lock.yaml, package-lock.json), não os mescle à mão; regenere-os:

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

Como mantenho a compatibilidade de versão?

O princípio é "adicionar, não alterar, não remover".

Mudança Compatibilidade Abordagem
Adicionar um campo opcional Compatível Adicione; consumidores ignoram campos desconhecidos
Adicionar um campo obrigatório Incompatível Suba a versão, ou forneça um padrão
Remover um campo Incompatível Marque como obsoleto primeiro, acompanhe os chamadores e depois remova
Mudar o tipo de um campo Incompatível Adicione um novo nome de campo e grave em duplicidade durante a transição
Mudar o conjunto de valores de enum Depende Adicionar valores é compatível; removê-los não é

Mecanismos de suporte:

  • Consumidores usam "análise leniente": ignoram campos desconhecidos e fornecem padrões para os ausentes;
  • O servidor mantém campos antigos por um tempo e retorna um cabeçalho Deprecation na resposta como dica;
  • No Schema, não marque campos novos como required, e etiquete campos obsoletos com deprecated: true.

Como valido contratos no CI?

Trate o Schema como parte do código e valide a cada commit.

# Valide dados de amostra contra o Schema (usando ajv-cli)
npx ajv-cli validate -s schemas/user.json -d "fixtures/users/*.json" --strict=false

Rode três coisas no CI:

  1. Validade do Schema em si: se a declaração $schema é válida e se $ref resolve;
  2. Se as fixtures correspondem ao Schema: garante que os dados de teste não derivem;
  3. Se o contrato é compatível retroativamente: compare com a versão anterior e verifique campos removidos ou tipos alterados.

O terceiro item precisa de suporte de ferramentas (como openapi-diff ou json-schema-diff); sem ele, ao menos compare mudanças nos arrays required e nos conjuntos de chaves de properties com um script.

3. Referência rápida

Pergunta Recomendação
Estrutura de resposta {"data":..., "meta":...}, com códigos de status HTTP para erros
Estilo de nomeação camelCase (frontend-first) ou snake_case (data-first), consistente em todo o site
Formato de hora Strings RFC 3339 com fuso horário, nomes de campo terminando em At
Nulos Use null para sem valor, omita campos irrelevantes, omissão em PATCH significa "não mudar"
Nomeação de booleanos Prefixos is/has/can, sem negações, sem 0/1
Enums Strings, minúsculas com underscores, adicione enum no Schema
Formato de configuração JSON para validação programática, TOML ou JSONC para edição manual
Fixtures Uma base + sobrescritas de diff mínimo, válidas contra o Schema
Conflitos de merge Unifique a formatação, ordene chaves, divida arquivos
Compatibilidade de versão Adicione, não altere, não exclua; marque obsoleto antes de remover