JSON Toolkit

Kluczowe punkty projektowania kontraktów danych API JSON

Podsumowanie: Sześć rzeczy bardzo trudno zmienić po wdrożeniu: koperta odpowiedzi, format błędów, kształt paginacji, reprezentacja identyfikatorów i czasu, semantyka null a brak pola oraz ewolucja wersji.

Średniozaawansowany~13 min czytaniaOpublikowano 2026-10-30Zaktualizowano 2026-10-30
Okładka artykułu o kluczowych punktach projektowania kontraktów danych API JSON

Najważniejsze: ustal kopertę odpowiedzi, format błędów, reprezentację identyfikatorów i czasu oraz semantykę null a brak pola w kontrakcie, zanim zaczniesz debatować nad paginacją i szczegółami nazewnictwa; zawsze owijaj wierzchołek w kopertę data, aby paginację i identyfikatory śledzenia można było dodać później bez łamania istniejących wywołań.

W projektowaniu API nie ma standardowych odpowiedzi, ale istnieją decyzje „trudne do cofnięcia po wyborze”, które najlepiej przemyśleć przed napisaniem pierwszej linii kodu.

1. Kopertować odpowiedź czy nie

Trzy style:

// 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" } }
Styl Zalety Wady
Odpowiedź naga Najprostsza, o warstwę mniej dla frontendu Nie można dołączyć metadanych; tablica wierzchołkowa niesie ryzyko JSON hijacking i ewolucji
Koperta data Łatwo dodać meta / errors / links Jedna warstwa zagnieżdżenia więcej, frontend musi pisać res.data.data
Data + metadane Najlepsza obserwowalność Wymaga ścisłego ustalenia, które pola należą do meta

Zalecenie: używaj jednolicie koperty data zarówno dla list, jak i szczegółów, i nigdy nie czyń wierzchołka tablicą. Dzięki temu dodanie paginacji lub identyfikatorów śledzenia później nigdy nie łamie kontraktu.

2. Format błędów: skopiuj RFC 9457

Wymyślanie własnej struktury błędów to ogromne źródło kosztu współpracy. RFC 9457 (które zastępuje RFC 7807) definiuje 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"
}
Pole Znaczenie Wymagane
type Stabilny URI typu problemu Zalecane
title Krótkie streszczenie czytelne dla człowieka Zalecane
status Kod statusu HTTP Zalecane
detail Konkretne wyjaśnienie, co się stało tym razem Opcjonalne
instance Instancja zasobu, która miała problem Opcjonalne
Pola rozszerzeń Dowolne pola biznesowe Opcjonalne

Pola rozszerzeń (takie jak balance / required powyżej) są dozwolone przez specyfikację, a frontend może ich użyć do precyzyjnych komunikatów, zamiast parsować tekst detail.

3. Paginacja: kursor bije offset

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

// Cursor pagination
{ "data": [], "page": { "nextCursor": "eyJpZCI6MTAwfQ", "hasMore": true } }
Wymiar Paginacja offset Paginacja kursorowa
Trudność implementacji Niska (LIMIT/OFFSET) Średnia (wymaga stabilnego klucza sortowania)
Wydajność na głębokich stronach Słaba (im większy offset, tym wolniej) Dobra
Gdy dane się zmieniają Duplikaty lub brakujące elementy Stabilna
Skok do strony Możliwy Niemożliwy
Wymaga całkowitej liczby Wymaga dodatkowego zliczania Niewymagane

Zalecenie: używaj paginacji offsetowej dla tabel administracyjnych (potrzebują skoków stron i sum), a kursorowej dla nieskończonych kanałów dla użytkowników.

4. Identyfikatory i czas

4.1 Zawsze używaj łańcuchów dla identyfikatorów

Uzasadnienie omówiono w artykule o typach: po stronie JavaScript wartości powyżej 2^53 po cichu tracą precyzję. Nawet jeśli obecny identyfikator to mała liczba całkowita autoinkrementacyjna, łańcuchy zostawiają miejsce na późniejsze przejście na identyfikatory snowflake.

{ "id": "1000000000000000001" }

4.2 Używaj łańcuchów RFC 3339 dla czasu

{
  "createdAt": "2026-10-30T10:41:45+08:00",
  "updatedAt": "2026-10-30T02:41:45Z"
}
  • Nie używaj numerycznych znaczników czasu w milisekundach: są trudne do odczytania, a jednostkę (sekundy/milisekundy) często się myli;
  • Określaj strefę czasową jawnie: albo przez Z, albo przez przesunięcie; nigdy nie używaj „czasu lokalnego bez strefy czasowej”;
  • Nazywaj pola jednolicie xxxAt, aby na pierwszy rzut oka było oczywiste, że to punkt w czasie.

5. null a brak pola

Musisz wybrać jedną zasadę i wpisać ją do dokumentacji:

Podejście Wyrażanie „braku wartości” Wyrażanie „nie modyfikuj”
Podejście A Wartość pola to null Pomiń pole w żądaniu
Podejście B Pole nie istnieje Pomiń pole w żądaniu

Problem z podejściem A polega na tym, że „pominięte” i „wysłane jako null” znaczą co innego w PATCH, co łatwo źle zinterpretować; problem z podejściem B polega na tym, że JSON.stringify w frontendzie pomija pola o wartości undefined, co przypadkiem pasuje do semantyki.

Praktyczne zalecenie:

  • Używaj podejścia A dla treści odpowiedzi (null jasno znaczy brak wartości, a frontend nie musi rozróżniać undefined);
  • Używaj podejścia B dla treści żądań (zwłaszcza PATCH), w połączeniu z Merge Patch lub JSON Patch, aby semantyka była jawna.

6. Nazewnictwo i struktura pól

Decyzja Typowe wybory Zastrzeżenie
Styl nazewnictwa camelCase (ekosystem JS) / snake_case (Python, bazy danych) Jednolicie w projekcie, nie mieszaj
Wartości wyliczeń Małymi literami z podkreśleniami, "pending_review" Nie używaj liczb; są trudne do odczytania
Pola logiczne isActive / enabled Nie używaj status: 0/1
Pola tablicowe Rzeczowniki w liczbie mnogiej, items Zwracaj [] dla pustych tablic, nigdy null
Głębokość zagnieżdżenia Nie więcej niż 3 poziomy Głębiej niż tak, podziel to albo zaproponuj parametr expand

Zwracanie [] zamiast null dla pustych tablic ma duże znaczenie: data.items.map(...) we frontendzie nie wysypie się z powodu null.

7. Strategia ewolucji

Zmiana Łamie zgodność Jak obsłużyć
Dodanie pola opcjonalnego Nie Wdróż bezpośrednio
Dodanie pola wymaganego Tak Dodaj wartość domyślną albo wydaj przez wersję
Usunięcie pola Tak Najpierw oznacz jako przestarzałe, obserwuj wywołania, potem usuń
Zmiana typu pola Tak Dodaj nową nazwę pola i podwójny zapis w okresie przejściowym
Zmiana zbioru wartości wyliczenia Zależy Klient musi mieć fallback dla nieznanych wartości
Zmiana semantyki pola Tak Traktuj jak nowe pole

Trzy zasady dyscypliny:

  1. Klienci muszą być tolerancyjni wobec nieznanych pól (nie zamykaj się w pułapkę przez additionalProperties: false);
  2. Klienci muszą mieć gałąź fallback dla nieznanych wartości wyliczeń;
  3. Przechowuj przestarzałe pola przez co najmniej jedną główną wersję i oznacz je nagłówkiem Deprecation w odpowiedziach lub dokumentacji.

8. Kompletny przykład

{
  "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 }
}

Ta odpowiedź odzwierciedla wszystkie powyższe konwencje: koperta data, identyfikatory łańcuchowe, kwoty jako łańcuchy, znaczniki czasu ze strefą czasową, null znaczące brak, pusta tablica, paginacja kursorowa i meta dla informacji obserwowalności.

9. Lista kontrolna

  • [ ] Wierzchołek nie jest tablicą; używaj jednolicie koperty data;
  • [ ] Odpowiedzi błędów są zgodne z RFC 9457 i zawierają stabilne type;
  • [ ] Kształt paginacji pasuje do scenariusza biznesowego;
  • [ ] Identyfikatory są łańcuchami, a czas używa RFC 3339;
  • [ ] Semantyka null i braku pola jest wpisana do dokumentacji;
  • [ ] Styl nazewnictwa jest jednolity w projekcie, a wyliczenia używają łańcuchów małymi literami;
  • [ ] Klient ma fallbacki dla nieznanych pól i nieznanych wartości wyliczeń;
  • [ ] Zmiany łamiące zgodność przechodzą przez wersjonowanie lub podwójny zapis.

FAQ

Jak zaprojektować ewoluowalne API JSON?

Owijaj jednolicie listy i szczegóły w kopertę data i nigdy nie czyń wierzchołka tablicą, aby paginację i identyfikatory śledzenia można było dodać bez łamania kontraktu. Używaj identyfikatorów łańcuchowych i znaczników czasu RFC 3339, a semantykę null a brak pola wpisz do dokumentacji. Zostaw nowe pola opcjonalne i semantycznie niezależne, a klientom daj fallbacki dla nieznanych pól i wartości wyliczeń.

Dlaczego nie wymyślać własnego formatu błędów?

Wymyślanie własnej struktury błędów to ogromne źródło kosztu współpracy. Skopiuj application/problem+json z RFC 9457, identyfikuj problemy stabilnym type i pozwalaj na pola rozszerzeń, takie jak balance i required, aby frontend mógł budować precyzyjne komunikaty bez parsowania tekstu detail.

Czy każde punkt końcowy powinien używać paginacji kursorowej?

Nie. Tabele administracyjne potrzebują skoków stron i sum, więc lepiej pasuje paginacja offsetowa; nieskończone kanały dla użytkowników powinny używać kursorowej, która jest stabilna na głębokich stronach i unika duplikatów lub luk przy zmianach danych. Wybieraj według tego, czy scenariusz wymaga skoków stron i sum.