JSON Toolkit

FAQ sulla collaborazione di squadra: contratti, configurazione e fixture

In sintesi: Stile dei nomi, formati delle date, semantica di null, forma degli enum, formati di configurazione, organizzazione dei fixture e conflitti di merge — dodici compromessi su cui le squadre discutono davvero.

Intermedio~12 min di letturaPubblicato il 2026-10-30Aggiornato il 2026-10-30
Copertina della FAQ sulla collaborazione di squadra

In sintesi: stabilisci una volta per tutte nella convenzione di squadra — un envelope {"data":…,"meta":…} con codici di stato HTTP, un unico stile di denominazione in tutto il sito, timestamp RFC 3339 con fuso orario e enum come stringhe — poi fai evolvere ogni nuova API contro lo stesso contratto solo aggiungendo campi, senza mai modificarli o eliminarli.

Non esiste un'unica risposta corretta a queste domande, ma una squadra deve avere un'unica risposta — altrimenti ogni nuova API riavvia la stessa discussione.

1. Contratti API

Quale struttura dovrebbe restituire un'API?

Tre forme comuni, ciascuna con un costo:

Forma Esempio Vantaggi Svantaggi
Dati grezzi [{...}, {...}] Semplice Non può veicolare metadati
Envelope fisso {"code":0,"message":"ok","data":{...}} Gestione uniforme degli errori Il codice di business duplica lo stato HTTP
Envelope di risorsa {"data":{...},"meta":{...},"links":{...}} Estensibile, semantica chiara Un livello di annidamento in più

Per le API di business interne, preferisci "envelope di risorsa + codici di stato HTTP che esprimono l'esito":

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

Per gli errori, usa un codice di stato HTTP più un corpo di errore strutturato invece di annidare un altro code:

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

Punto chiave: i codici di stato HTTP esprimono già successo o insuccesso, quindi un codice di business è ridondante. Quando i due sistemi convivono, incoerenze come "201 ma il code è 500" sono quasi inevitabili.

Evita gli array nudi. Con un array al livello superiore non puoi allegare paginazione, totali o cursori, e aggiungerli in seguito significa modificare ogni chiamante.

I nomi dei campi dovrebbero usare snake_case o camelCase?

Scegline uno, applicalo in tutto il sito e non mescolarli mai all'interno di un singolo documento JSON.

Stile Ecosistema comune Esempio
camelCase JavaScript / TypeScript / Java userName, createdAt
snake_case Python / Ruby / Go (in parte) / colonne di database user_name, created_at
kebab-case Raro; le chiavi con trattino sono scomode da accedere user-name

Criteri:

  1. Chi consuma questo JSON: scegli camelCase quando domina il frontend, snake_case quando dominano pipeline di dati e servizi Python;
  2. Si mappa direttamente sulle colonne del database: snake_case risparmia uno strato di conversione quando si passano direttamente le strutture delle tabelle;
  3. Continuità dei sistemi esistenti: mantieni ciò che usa l'API esistente; la coerenza batte la correttezza.

Se devi davvero convertire, fallo al confine (gateway o livello delle richieste del frontend) invece di spargere chiamate toCamelCase nel codice di business.

Che formato dovrebbero usare i campi temporali?

Usa stringhe RFC 3339 / ISO 8601 con un offset di fuso orario:

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

Punti chiave:

  • Includi il fuso orario: 2026-10-30 11:37:57 senza fuso orario si romperà inevitabilmente negli scenari tra fusi orari diversi;
  • Usa Z o un offset in modo coerente: entrambi sono corretti, ma non mescolarli all'interno della stessa API;
  • Evita i timestamp numerici: che siano secondi o millisecondi, e che includano o meno i secondi intercalari, ogni squadra deve reinterpretarli. Quando serve compattezza, usa un intero in millisecondi, ma fissa l'unità nella documentazione.

Preferisci nomi di campo con suffisso At come createdAt / updatedAt / deletedAt, così è evidente a colpo d'occhio che il valore è un istante nel tempo e non una durata. Per le durate usa timeoutMs, ttlSeconds — metti l'unità nel nome.

I null dovrebbero essere null o un campo omesso?

Dipende da se "questo campo ha un significato":

Semantica Forma Note
Il campo esiste ma non ha ancora un valore "name": null Dice esplicitamente "è stato controllato ed è vuoto"
Il campo non è pertinente a questa risorsa Omesso Per esempio, un utente normale non dovrebbe avere vipExpireAt
Aggiornamento parziale (PATCH) Omesso = non modificare Qui solo null significa "svuota"

Il terzo caso è quello più spesso trascurato. In una richiesta PATCH:

{ "nickname": null }

questo significa "svuota il nickname", mentre lasciare nickname fuori dal corpo della richiesta significa "non toccare il nickname". Se tratti entrambi come "svuota", un utente che cambia la propria email cancellerà il proprio nickname.

Ancora una volta: non usare undefined. Non è un valore JSON valido, e JSON.stringify elimina del tutto la chiave, quindi il comportamento corrisponde a "omesso" mentre il codice sembra dire "è stato passato esplicitamente un valore vuoto".

Come dovrebbero essere denominati i campi booleani?

Usa i prefissi is / has / can / should ed evita le negazioni.

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

Tre lezioni:

  1. Niente negazioni: isDisabled: false costringe il lettore a invertirlo una volta; isEnabled: true è più diretto. La doppia negazione (isNotDisabled) è un disastro;
  2. Non usare numeri per i booleani: "enabled": 1 fa sì che ogni consumatore scriva === 1 una volta;
  3. Non usare stringhe: "enable": "true" è truthy in JavaScript, e if (data.enable) è vero anche per "false"; bug di questo tipo sono estremamente difficili da rintracciare.

I campi enum dovrebbero usare numeri o stringhe?

Usa le stringhe, a meno che non vi sia un chiaro vincolo di archiviazione o larghezza di banda.

{ "status": "active" }

Rispetto a { "status": 1 }, le stringhe vincono perché:

  • Log, catture di pacchetti e database sono direttamente leggibili senza consultare una tabella di mapping;
  • Aggiungere un valore enum non cambia il significato dei valori esistenti (inserire un valore nel mezzo di un enum numerico sposta tutto);
  • Il frontend non deve mantenere sincronizzata una mappatura 1 → 'active'.

Il costo sono qualche byte in più, quasi trascurabile dopo la compressione.

Pratica complementare: usa minuscole + underscore per i valori enum stringa (pending_review), e vincolali con enum nello Schema così i valori invalidi vengono rifiutati in fase di validazione.

2. Configurazione e fixture

La configurazione dovrebbe essere JSON, YAML o TOML?

Scegli per scopo, non per preferenza:

Formato Adatto a Inadatto a
JSON Lettura e scrittura programmatica, dati strettamente strutturati, validazione con Schema Modifica manuale frequente (i commenti non sono ammessi)
YAML Configurazione di deployment profondamente annidata (K8s) L'indentazione scritta a mano è soggetta a errori; yes/no diventano booleani
TOML Configurazione piatta di dipendenze e strumenti (Cargo.toml, pyproject.toml) Leggibilità scarsa per annidamenti profondi
JSONC Configurazione dell'editor che richiede commenti (tsconfig.json) Attraversare i confini di sistema

Un errore comune nelle squadre è archiviare la configurazione di business in YAML — la conversione implicita dei tipi di YAML (1.0 diventa un numero, NO diventa un booleano, 2026-10-30 diventa una data) produce risultati diversi tra parser.

Se la configurazione deve essere validata rigorosamente da un programma, scegli JSON + Schema, e documenta i commenti tramite un campo _comment o documentazione separata.

Come dovrebbero essere organizzati i fixture di test?

Organizza per "una base + diff minimi", non copiando un enorme JSON per ogni caso di test.

fixtures/
├─ users/
│  ├─ base.json              # Baseline user object
│  ├─ no-email.json          # Only email is missing
│  └─ invalid-age.json       # Only age is out of range
└─ fixtures.ts               # Utility functions for loading and shallow merging
import baseUser from './users/base.json';

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

Così un nuovo caso di test deve scrivere solo i campi che differiscono, e modificare un campo della base aggiorna automaticamente ogni caso.

Tre regole:

  1. I fixture devono superare la validazione dello Schema: il fixture base stesso deve essere dati validi;
  2. Non esportare dati reali dalla produzione: numeri di telefono, numeri di documento e indirizzi sono tutti dati personali;
  3. I fixture appartengono al controllo di versione ma devono restare piccoli: un singolo file oltre qualche decina di KB merita di essere ridotto o generato.

Come posso ridurre i conflitti di merge nei file JSON?

JSON va facilmente in conflitto: un formattatore riformatta l'intero file in una volta, quindi due persone che modificano ciascuna una riga entrano in conflitto sull'intero file.

Quattro tattiche:

  1. Unifica la formattazione e aggiungila a .editorconfig: indentazione, terminatori di riga e newline finale coerenti evitano diff non pertinenti;
  2. Ordina le chiavi: concorda un ordine alfabetico così un nuovo campo non collide con il punto di inserimento di qualcun altro;
  3. Dividi i file: spezza un enorme config.json in più file per dominio, e la superficie di conflitto si riduce naturalmente;
  4. Evita i commit "riformatta tutto": separa la formattazione dalle modifiche di business in due commit così la revisione resta leggibile.

Se la squadra usa file di lock (pnpm-lock.yaml, package-lock.json), non fare il merge a mano; rigenerali:

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

Come posso mantenere la compatibilità delle versioni?

Il principio è "aggiungi, non modificare, non rimuovere".

Modifica Compatibilità Approccio
Aggiungere un campo opzionale Compatibile Aggiungilo; i consumatori ignorano i campi sconosciuti
Aggiungere un campo obbligatorio Incompatibile Incrementa la versione, oppure fornisci un default
Rimuovere un campo Incompatibile Marchialo prima come deprecato, monitora i chiamanti, poi rimuovilo
Cambiare il tipo di un campo Incompatibile Aggiungi un nuovo nome di campo e scrivi in doppio durante la transizione
Cambiare l'insieme dei valori enum Dipende Aggiungere valori è compatibile; rimuoverli non lo è

Meccanismi di supporto:

  • I consumatori usano un "parsing indulgente": ignorano i campi sconosciuti e forniscono default per quelli mancanti;
  • Il server mantiene i vecchi campi per un po' e restituisce un header Deprecation nella risposta come suggerimento;
  • Nello Schema, non marcare i nuovi campi come required, e contrassegna i campi deprecati con deprecated: true.

Come posso validare i contratti in CI?

Tratta lo Schema come parte del codice e validalo a ogni commit.

# Validate sample data against the Schema (using ajv-cli)
npx ajv-cli validate -s schemas/user.json -d "fixtures/users/*.json" --strict=false

Esegui tre cose in CI:

  1. Validità dello Schema stesso: se la dichiarazione $schema è valida e se $ref si risolve;
  2. Se i fixture corrispondono allo Schema: assicura che i dati di test non derivino;
  3. Se il contratto è retrocompatibile: confronta con la versione precedente e controlla campi rimossi o tipi modificati.

Il terzo elemento richiede supporto di strumenti (come openapi-diff o json-schema-diff); senza di essi, almeno confronta con uno script i cambiamenti negli array required e negli insiemi di chiavi di properties.