JSON Toolkit

Punti chiave per progettare contratti di dati API JSON

In sintesi: Sei cose sono molto difficili da cambiare una volta rilasciate: envelope delle risposte, formati degli errori, forma della paginazione, rappresentazione di ID e tempo, semantica di null versus assenza, ed evoluzione delle versioni.

Intermedio~13 min di letturaPubblicato il 2026-10-30Aggiornato il 2026-10-30
Copertina dei punti chiave per progettare contratti di dati API JSON

In sintesi: fissa nel contratto l'envelope delle risposte, il formato degli errori, la rappresentazione di ID e tempo e la semantica di null-versus-assenza prima di discutere i dettagli di paginazione e denominazione; avvolgi sempre il livello superiore in un envelope data così paginazione e ID di tracciamento possono essere aggiunti dopo senza rompere i chiamanti esistenti.

Non esistono risposte standard nella progettazione API, ma ci sono alcune decisioni "difficili da ritirare una volta scelte" che è meglio pensare prima di scrivere la prima riga di codice.

1. Avvolgere o no la risposta

Tre stili:

// Style A: bare response
{ "id": 1, "name": "张三" }

// Style B: data envelope
{ "data": { "id": 1, "name": "张三" } }

// Style C: data + metadata
{ "data": { "id": 1, "name": "张三" }, "meta": { "requestId": "a1b2" } }
Stile Vantaggi Svantaggi
Risposta nuda Il più semplice, un livello in meno per il frontend Non può allegare metadati; un array al livello superiore comporta rischi di dirottamento JSON ed evoluzione
Envelope data Facile aggiungere meta / errors / links Un livello di annidamento in più, il frontend deve scrivere res.data.data
Dati + metadati Migliore osservabilità Richiede un accordo rigoroso su quali campi appartengono a meta

Raccomandazione: usa un envelope data in modo uniforme sia per le liste sia per i dettagli, e non rendere mai il livello superiore un array. Così, aggiungere paginazione o ID di tracciamento in seguito non rompe mai il contratto.

2. Formato degli errori: copia RFC 9457

Inventare la propria struttura di errore è un'enorme fonte di costo di collaborazione. RFC 9457 (che sostituisce RFC 7807) definisce application/problem+json:

{
  "type": "https://bbdrug.com/problems/insufficient-balance",
  "title": "余额不足",
  "status": 422,
  "detail": "账户余额 12.00 元,本次扣款需要 30.00 元",
  "instance": "/accounts/A1001/payments/p-889",
  "balance": "12.00",
  "required": "30.00"
}
Campo Significato Obbligatorio
type URI stabile del tipo di problema Raccomandato
title Breve riassunto leggibile dagli umani Raccomandato
status Codice di stato HTTP Raccomandato
detail Spiegazione specifica di cosa è successo questa volta Opzionale
instance L'istanza di risorsa che ha avuto il problema Opzionale
Campi di estensione Campi di business arbitrari Opzionale

I campi di estensione (come balance / required sopra) sono consentiti dalla specifica, e il frontend può usarli per messaggi precisi invece di analizzare il testo di detail.

3. Paginazione: il cursore batte l'offset

// Offset pagination
{ "data": [], "page": { "offset": 40, "limit": 20, "total": 137 } }

// Cursor pagination
{ "data": [], "page": { "nextCursor": "eyJpZCI6MTAwfQ", "hasMore": true } }
Dimensione Paginazione offset Paginazione a cursore
Difficoltà implementativa Bassa (LIMIT/OFFSET) Media (richiede una chiave di ordinamento stabile)
Prestazioni su pagine profonde Scarse (più grande l'offset, più lento) Buone
Quando i dati cambiano Duplicati o elementi mancanti Stabile
Salto a una pagina Possibile Non possibile
Necessita di un conteggio totale Richiede un conteggio extra Non richiesto

Raccomandazione: usa la paginazione offset per le tabelle di amministrazione (necessitano salti di pagina e totali), e la paginazione a cursore per i feed infiniti rivolti all'utente.

4. ID e tempo

4.1 Usa sempre stringhe per gli ID

Il ragionamento è trattato nell'articolo sui tipi: sul lato JavaScript, i valori sopra 2^53 perdono silenziosamente precisione. Anche se l'ID attuale è un piccolo intero auto-incrementale, usare stringhe lascia spazio per passare in seguito a ID snowflake.

{ "id": "1000000000000000001" }

4.2 Usa stringhe RFC 3339 per il tempo

{
  "createdAt": "2026-10-30T10:41:45+08:00",
  "updatedAt": "2026-10-30T02:41:45Z"
}
  • Non usare numeri timestamp in millisecondi: sono difficili da leggere, e l'unità (secondi/millisecondi) viene spesso confusa;
  • Specifica esplicitamente il fuso orario: o con Z o con un offset; non usare mai "ora locale senza fuso orario";
  • Nomina i campi in modo uniforme come xxxAt così è evidente a colpo d'occhio che si tratta di un istante nel tempo.

5. null e assente

Devi sceglierne uno e scriverlo nella documentazione:

Approccio Esprimere "nessun valore" Esprimere "non modificare"
Approccio A Il valore del campo è null Omettere il campo nella richiesta
Approccio B Il campo non esiste Omettere il campo nella richiesta

Il problema dell'Approccio A è che "omesso" e "inviato come null" significano cose diverse in un PATCH, il che è facile da fraintendere; il problema dell'Approccio B è che il JSON.stringify del frontend elimina i campi il cui valore è undefined, il che per caso corrisponde alla semantica.

Raccomandazione pratica:

  • Usa l'Approccio A per i corpi delle risposte (null significa chiaramente nessun valore, e il frontend non deve distinguere undefined);
  • Usa l'Approccio B per i corpi delle richieste (specialmente PATCH), combinato con Merge Patch o JSON Patch per rendere esplicita la semantica.

6. Denominazione e struttura dei campi

Decisione Scelte comuni Avvertenza
Stile di denominazione camelCase (ecosistema JS) / snake_case (Python, database) Uniforme in tutto il progetto, non mescolare
Valori enum Minuscole con underscore, "pending_review" Non usare numeri; sono difficili da leggere
Campi booleani isActive / enabled Non usare status: 0/1
Campi array Nomi plurali, items Restituisci [] per gli array vuoti, mai null
Profondità di annidamento Non più di 3 livelli Oltre, dividi o offri un parametro expand

Restituire [] invece di null per gli array vuoti conta molto: data.items.map(...) sul frontend non andrà in crash per un null.

7. Strategia di evoluzione

Modifica Rompe la compatibilità Come gestirla
Aggiungere un campo opzionale No Spediscilo direttamente
Aggiungere un campo obbligatorio Sì Aggiungi un valore default, o rilascia per versione
Rimuovere un campo Sì Marchialo prima come deprecato, osserva i chiamanti, poi rimuovilo
Cambiare il tipo di un campo Sì Aggiungi un nuovo nome di campo e scrivi in doppio durante la transizione
Cambiare l'insieme dei valori enum Dipende Il client deve avere un ripiego per i valori sconosciuti
Cambiare la semantica di un campo Sì Trattalo come un nuovo campo

Tre regole di disciplina:

  1. I client devono essere tolleranti ai campi sconosciuti (non chiuderti con additionalProperties: false);
  2. I client devono avere un ramo di ripiego per i valori enum sconosciuti;
  3. Mantieni i campi deprecati per almeno una versione major, e marchiali con Deprecation negli header delle risposte o nella documentazione.

8. Un esempio completo

{
  "data": [
    {
      "id": "1001",
      "name": "JSON 权威指南",
      "price": "89.50",
      "tags": ["json", "spec"],
      "publishedAt": "2026-09-01T00:00:00+08:00",
      "author": null
    }
  ],
  "page": { "nextCursor": null, "hasMore": false },
  "meta": { "requestId": "req-7f3a", "tookMs": 12 }
}

Questa risposta riflette tutte le convenzioni sopra: envelope data, ID stringa, importi come stringhe, timestamp con fuso orario, null che significa mancante, array vuoto, paginazione a cursore, e meta per le informazioni di osservabilità.

9. Lista di controllo

  • [ ] Il livello superiore non è un array; usa un envelope data in modo uniforme;
  • [ ] Le risposte di errore seguono RFC 9457 e includono un type stabile;
  • [ ] La forma della paginazione corrisponde allo scenario di business;
  • [ ] Gli ID sono stringhe, e il tempo usa RFC 3339;
  • [ ] La semantica di null e assenza è scritta nella documentazione;
  • [ ] Lo stile di denominazione è uniforme in tutto il progetto, e gli enum usano stringhe minuscole;
  • [ ] Il client ha ripieghi per campi sconosciuti e valori enum sconosciuti;
  • [ ] Le modifiche che rompono passano attraverso versioni o una transizione a doppia scrittura.

FAQ

Come dovrei progettare un'API JSON evolvibile?

Avvolgi liste e dettagli in modo uniforme in un envelope data e non rendere mai il livello superiore un array, così paginazione e ID di tracciamento possono essere aggiunti senza rompere il contratto. Usa ID stringa e timestamp RFC 3339, e scrivi nella documentazione la semantica di null-versus-assenza. Mantieni i nuovi campi opzionali e semanticamente indipendenti, e dai ai client ripieghi per campi sconosciuti e valori enum.

Perché non inventare un formato di errore personalizzato?

Inventare la propria struttura di errore è un'enorme fonte di costo di collaborazione. Copia application/problem+json di RFC 9457, identifica i problemi con un type stabile, e consenti campi di estensione come balance e required, così il frontend può costruire messaggi precisi senza analizzare il testo di detail.

Ogni endpoint dovrebbe usare la paginazione a cursore?

No. Le tabelle di amministrazione necessitano salti di pagina e totali, quindi la paginazione offset si adatta meglio; i feed infiniti rivolti all'utente dovrebbero usare la paginazione a cursore, che resta stabile sulle pagine profonde ed evita duplicati o buchi quando i dati cambiano. Scegli in base a se lo scenario necessita salti di pagina e totali.

Riferimenti

Strumenti correlati

Articoli correlati

Sei tipi, precisione numerica e trappole della codifica

JSON ha solo sei tipi, ma ciascuno può rimodellare i tuoi dati senza che tu te ne accorga. Questo articolo copre in un colpo solo le trappole di numeri, stringhe e null.

Principiante~12 min di letturaAggiornato il 2026-10-30

JSON Pointer, Patch e Merge Patch

Tre specifiche brevi formano un protocollo di cambiamento: Pointer localizza, Patch modifica con precisione, Merge Patch unisce parzialmente. Scegliere male danneggia array e concorrenza.

Intermedio~12 min di letturaAggiornato il 2026-10-30

Gestire JSON per configurazione, fixture e dati locali

La configurazione deve essere stratificata e validata; i fixture devono essere deterministici, leggibili e diffabili. Se questi due tipi di file vengono gestiti male, i costi di troubleshooting restano alti a lungo.

Principiante~10 min di letturaAggiornato il 2026-10-30