JSON Toolkit

Perkara Penting untuk Mereka Bentuk Kontrak Data JSON API

Inti pati: Enam perkara sangat sukar diubah selepas dihantar: sampul respons, format kesalahan, bentuk penomboran halaman, perwakilan ID dan masa, semantik null berbanding ketiadaan, dan evolusi versi.

Pertengahan~13 min bacaanDiterbitkan 2026-10-30Dikemas kini 2026-10-30
Kulit perkara penting untuk mereka bentuk kontrak data JSON API

Kesimpulan utama: kunci sampul respons, format kesalahan, perwakilan ID dan masa, serta semantik null berbanding ketiadaan dalam kontrak sebelum berbincang butiran penomboran halaman dan penamaan; sentiasa balut tahap atas dalam sampul data supaya penomboran halaman dan ID penjejakan boleh ditambah kemudian tanpa memecahkan pemanggil sedia ada.

Tiada jawapan standard dalam reka bentuk API, tetapi ada keputusan "sukar ditarik balik selepas dipilih" yang lebih baik difikirkan sebelum anda menulis baris kod pertama.

1. Sampul respons atau tidak

Tiga gaya:

// 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" } }
Gaya Kebaikan Keburukan
Respons kosong Paling ringkas, satu lapisan kurang untuk bahagian hadapan Tidak boleh lampirkan metadata; tatasusunan tahap atas membawa risiko perampasan JSON dan evolusi
Sampul data Mudah menambah meta / errors / links Satu lagi tahap bersarang, bahagian hadapan mesti menulis res.data.data
Data + metadata Kebolehan cerapan terbaik Memerlukan persetujuan ketat tentang medan mana yang masuk meta

Syor: gunakan sampul data secara seragam untuk senarai dan butiran, dan jangan sekali-kali menjadikan tahap atas sebagai tatasusunan. Dengan itu, menambah penomboran halaman atau ID penjejakan kemudian tidak pernah memecahkan kontrak.

2. Format kesalahan: salin RFC 9457

Mencipta struktur kesalahan sendiri ialah punca besar kos kerjasama. RFC 9457 (yang menggantikan RFC 7807) mentakrifkan 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"
}
Medan Maksud Diperlukan
type URI jenis masalah yang stabil Disyorkan
title Ringkasan pendek yang boleh dibaca manusia Disyorkan
status Kod status HTTP Disyorkan
detail Penjelasan khusus apa yang berlaku kali ini Pilihan
instance Instans sumber yang bermasalah Pilihan
Medan sambungan Medan perniagaan arbitrari Pilihan

Medan sambungan (seperti balance / required di atas) dibenarkan oleh spesifikasi, dan bahagian hadapan boleh menggunakannya untuk mesej tepat dan bukannya menghurai teks detail.

3. Penomboran halaman: kursor mengalahkan offset

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

// Cursor pagination
{ "data": [], "page": { "nextCursor": "eyJpZCI6MTAwfQ", "hasMore": true } }
Dimensi Penomboran halaman offset Penomboran halaman kursor
Kesukaran pelaksanaan Rendah (LIMIT/OFFSET) Sederhana (memerlukan kunci isihan stabil)
Prestasi pada halaman dalam Buruk (semakin besar offset, semakin perlahan) Baik
Apabila data berubah Pendua atau item hilang Stabil
Lompat ke halaman Mungkin Tidak mungkin
Memerlukan jumlah kiraan Memerlukan kiraan tambahan Tidak diperlukan

Syor: gunakan penomboran halaman offset untuk jadual pentadbir (ia memerlukan lompatan halaman dan jumlah), dan penomboran halaman kursor untuk suapan infiniti yang menghadap pengguna.

4. ID dan masa

4.1 Sentiasa gunakan string untuk ID

Alasannya dibincangkan dalam artikel tentang jenis: pada bahagian JavaScript, nilai melebihi 2^53 kehilangan ketepatan secara senyap. Walaupun ID semasa ialah integer auto-inkremen kecil, menggunakan string meninggalkan ruang untuk beralih ke ID snowflake kemudian.

{ "id": "1000000000000000001" }

4.2 Gunakan string RFC 3339 untuk masa

{
  "createdAt": "2026-10-30T10:41:45+08:00",
  "updatedAt": "2026-10-30T02:41:45Z"
}
  • Jangan gunakan nombor cap masa milisaat: ia sukar dibaca, dan unitnya (saat/milisaat) sering tersalah;
  • Nyatakan zon masa secara eksplisit: sama ada dengan Z atau dengan offset; jangan sekali-kali menggunakan "masa setempat tanpa zon masa";
  • Namakan medan secara seragam sebagai xxxAt supaya jelas sekali pandang bahawa ia titik masa.

5. null dan ketiadaan

Anda mesti memilih satu dan menulisnya ke dalam dokumentasi:

Pendekatan Menyatakan "tiada nilai" Menyatakan "jangan ubah"
Pendekatan A Nilai medan ialah null Abaikan medan dalam permintaan
Pendekatan B Medan tidak wujud Abaikan medan dalam permintaan

Masalah dengan Pendekatan A ialah "diabaikan" dan "dihantar sebagai null" bermaksud perkara berbeza dalam PATCH, yang mudah disalahtafsir; masalah dengan Pendekatan B ialah JSON.stringify bahagian hadapan menggugurkan medan yang nilainya undefined, yang kebetulan sepadan dengan semantik.

Syor praktikal:

  • Gunakan Pendekatan A untuk badan respons (null jelas bermaksud tiada nilai, dan bahagian hadapan tidak perlu membezakan undefined);
  • Gunakan Pendekatan B untuk badan permintaan (terutamanya PATCH), digabungkan dengan Merge Patch atau JSON Patch untuk menjadikan semantik eksplisit.

6. Penamaan medan dan struktur

Keputusan Pilihan biasa Awas
Gaya penamaan camelCase (ekosistem JS) / snake_case (Python, pangkalan data) Seragam merentasi projek, jangan campur
Nilai enum Huruf kecil dengan garis bawah, "pending_review" Jangan gunakan nombor; ia sukar dibaca
Medan boolean isActive / enabled Jangan gunakan status: 0/1
Medan tatasusunan Kata nama majmuk, items Pulangkan [] untuk tatasusunan kosong, jangan sekali-kali null
Kedalaman bersarang Tidak melebihi 3 tahap Lebih dalam daripada itu, pecahkan atau tawarkan parameter expand

Memulangkan [] dan bukannya null untuk tatasusunan kosong sangat penting: data.items.map(...) pada bahagian hadapan tidak akan ranap kerana null.

7. Strategi evolusi

Perubahan Memecahkan keserasian Cara mengendalikan
Tambah medan pilihan Tidak Hantar terus
Tambah medan wajib Ya Tambah nilai lalai, atau lepaskan mengikut versi
Buang medan Ya Tandakan usang dahulu, perhatikan pemanggil, kemudian buang
Tukar jenis medan Ya Tambah nama medan baharu dan tulis berganda semasa peralihan
Tukar set nilai enum Bergantung Klien mesti mempunyai sandaran untuk nilai tidak dikenali
Tukar semantik medan Ya Layan sebagai medan baharu

Tiga peraturan disiplin:

  1. Klien mesti toleran terhadap medan tidak dikenali (jangan kurung diri anda dengan additionalProperties: false);
  2. Klien mesti mempunyai cabang sandaran untuk nilai enum tidak dikenali;
  3. Kekalkan medan usang selama sekurang-kurangnya satu versi utama, dan tandakannya dengan Deprecation dalam pengepala respons atau dokumentasi.

8. Contoh lengkap

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

Respons ini mencerminkan semua konvensyen di atas: sampul data, ID string, jumlah sebagai string, cap masa dengan zon masa, null bermaksud tiada, tatasusunan kosong, penomboran halaman kursor, dan meta untuk maklumat kebolehan cerapan.

9. Senarai semak

  • [ ] Tahap atas bukan tatasusunan; gunakan sampul data secara seragam;
  • [ ] Respons kesalahan mengikut RFC 9457 dan termasuk type yang stabil;
  • [ ] Bentuk penomboran halaman sepadan dengan senario perniagaan;
  • [ ] ID ialah string, dan masa menggunakan RFC 3339;
  • [ ] Semantik null dan ketiadaan ditulis ke dalam dokumentasi;
  • [ ] Gaya penamaan seragam merentasi projek, dan enum menggunakan string huruf kecil;
  • [ ] Klien mempunyai sandaran untuk medan tidak dikenali dan nilai enum tidak dikenali;
  • [ ] Perubahan memecahkan melalui versi atau peralihan tulis berganda.

FAQ

Bagaimana saya harus mereka bentuk JSON API yang boleh berevolusi?

Balut senarai dan butiran secara seragam dalam sampul data dan jangan sekali-kali menjadikan tahap atas tatasusunan, supaya penomboran halaman dan ID penjejakan boleh ditambah tanpa memecahkan kontrak. Gunakan ID string dan cap masa RFC 3339, dan tulis semantik null berbanding ketiadaan ke dalam dokumentasi. Kekalkan medan baharu sebagai pilihan dan bebas semantik, dan beri klien sandaran untuk medan tidak dikenali dan nilai enum.

Mengapa tidak mencipta format kesalahan tersuai?

Mencipta struktur kesalahan sendiri ialah punca besar kos kerjasama. Salin application/problem+json RFC 9457, kenal pasti masalah dengan type yang stabil, dan benarkan medan sambungan seperti balance dan required, supaya bahagian hadapan boleh membina mesej tepat tanpa menghurai teks detail.

Patutkah setiap endpoint menggunakan penomboran halaman kursor?

Tidak. Jadual pentadbir memerlukan lompatan halaman dan jumlah, jadi penomboran halaman offset lebih sesuai; suapan infiniti yang menghadap pengguna harus menggunakan penomboran halaman kursor, yang kekal stabil pada halaman dalam dan mengelakkan pendua atau jurang apabila data berubah. Pilih berdasarkan sama ada senario memerlukan lompatan halaman dan jumlah.