Soalan Lazim Pertanyaan dan Pengesahan: JSONPath dan Schema
Inti pati: Sama ada pertanyaan laluan patut menggunakan JSONPath atau Pointer, bagaimana memilih antara Patch dan Merge Patch, apa yang boleh disekat oleh Schema, dan bagaimana memilih versi draft — semuanya dijelaskan dalam satu bacaan.

Pokoknya: gunakan JSONPath untuk mencari nod mengikut keadaan, JSON Pointer atau JSON Patch untuk menangani dan menyunting lokasi tepat, dan Merge Patch untuk menggabungkan konfigurasi; kunci Schema baharu kepada 2020-12 dan hidupkan allErrors supaya setiap kegagalan kembali dalam satu pusingan.
Pertanyaan ialah "cari ia"; pengesahan ialah "sahkan ia betul". Masing-masing mempunyai standard sendiri, jadi jangan campurkan.
1. Pertanyaan laluan
Bagaimana saya patut memilih antara JSONPath dan JSON Pointer?
Ia bergantung pada sama ada anda "mencari mengikut keadaan" atau "mengambil mengikut kedudukan".
| Dimensi | JSONPath | JSON Pointer |
|---|---|---|
| Standard | RFC 9535 (2024) | RFC 6901 (2013) |
| Ungkapan | $.users[?(@.age > 18)].id |
/users/0/id |
| Kad bebas dan rekursi | Menyokong *, .. |
Tidak disokong |
| Penapisan bersyarat | Disokong | Tidak disokong |
| Hasil | Satu set nod (mungkin beberapa) | Satu lokasi tunggal |
| Keupayaan menyatakan | Kuat, tetapi dengan beban perbezaan pelaksanaan yang besar | Lemah, tetapi benar-benar deterministik |
| Kegunaan biasa | Pengekstrakan log, penyahpepijatan, penerokaan data | JSON Patch, penanganan konfigurasi, laluan ralat |
Peraturan pemilihan adalah mudah:
- Perlu "mendapatkan semua nod yang sepadan dengan keadaan" → JSONPath;
- Perlu "menetapkan satu lokasi tepat dan menambah, membuang, atau mengubahnya" → JSON Pointer;
- Gunakan bersama: cari nod dengan JSONPath dan huraikan lokasinya dengan Pointer.
Jangan gunakan JSONPath untuk penanganan dalam kontrak API. JSONPath mengembalikan satu set, dan tiga cabang untuk hasil kosong, satu elemen, dan berbilang elemen menjadikan protokol rumit; Pointer bermaksud "sama ada ia menunjuk kepada lokasi yang wujud atau ia tidak wujud", yang lebih sesuai untuk kontrak.
Apakah perbezaan antara $.. dan $.*?
$.. ialah penurunan rekursif dan merayau setiap tahap; $.* hanya mengembalikan anak langsung tahap semasa.
{
"a": { "id": 1, "child": { "id": 2 } },
"b": { "id": 3 }
}
$.*→ dua objek{id:1, child:{...}}dan{id:3};$..id→ tiga nilai:1,2,3;$.*.id→1dan3, tidak termasuk2yang bersarang.
Penurunan rekursif berguna tetapi mahal: $.. merayau seluruh pokok, iaitu O(n) pada dokumen besar, dan ia mudah mengambil medan dalam yang tidak anda maksudkan.
Jika tahap konkrit berfungsi, elakkan ...
Bolehkah penapis membandingkan rentetan? Bolehkah ia memadan dengan regex?
Perbandingan rentetan berfungsi; regex bergantung pada pelaksanaan.
Sintaks standard (RFC 9535):
$.items[?(@.status == "active")]
$.items[?(@.price > 10 && @.stock > 0)]
$.items[?(@.name == $.target)] // bandingkan dengan nod lain
Perbandingan rentetan menggunakan petikan berganda (petikan tunggal juga dibenarkan dalam RFC 9535, tetapi sesetengah pelaksanaan lama hanya menerima petikan berganda).
Perbandingan berangka mengikut semantik berangka; mencampurkan rentetan dengan nombor, seperti "10" > 9, ialah tingkah laku tidak ditentukan dalam standard, jadi jangan bergantung padanya.
Untuk regex, RFC 9535 mentakrifkan fungsi match() dan search(), tetapi sokongannya berbeza-beza dengan ketara antara pelaksanaan.
Pendekatan selamat: biarkan JSONPath melakukan penapisan kasar, dan lakukan padanan regex dalam kod selepas anda mendapatkan hasilnya.
Patutkah saya menggunakan JSON Patch atau Merge Patch?
Kedua-duanya menghuraikan "bagaimana mengubah sesuatu", tetapi keupayaan menyatakan dan semantiknya benar-benar berbeza.
| Dimensi | JSON Patch (RFC 6902) | Merge Patch (RFC 7386) |
|---|---|---|
| Bentuk | Tatasusunan operasi [{"op":"add",...}] |
Objek separa |
| Operasi yang disokong | add / remove / replace / move / copy / test | Gabung sahaja (pemadaman melalui null) |
| Boleh menangani elemen tatasusunan | Ya, mengikut indeks | Tidak, seluruh tatasusunan diganti |
| Keatomikan | test membolehkan penguncian optimistik |
Tiada |
| Saiz dokumen | Besar tetapi tepat | Kecil tetapi terhad dari segi keupayaan menyatakan |
| Sesuai untuk | Suntingan halus, kemas kini separa API | Penggabungan konfigurasi, penghantaran borang separa |
Kriteria keputusan:
- Untuk mengubah medan elemen ketiga dalam tatasusunan → JSON Patch;
- Untuk menggabungkan beberapa entri konfigurasi ke dalam konfigurasi sedia ada → Merge Patch;
- Untuk mendapatkan kawalan konkurensi "gagal jika nilai asal bukan X" →
testJSON Patch.
Perhatikan perangkap dalam semantik null Merge Patch: null bermaksud "padam kunci ini", bukan "tetapkan nilai kepada null".
Menetapkan nilai kepada null adalah mustahil dengan Merge Patch.
2. Pengesahan Schema
Apakah masalah yang diselesaikan oleh JSON Schema?
Ia mengubah "bagaimana data sepatutnya kelihatan" daripada persetujuan lisan kepada kontrak yang boleh disemak oleh mesin. Ralat yang sebenarnya boleh disekatnya:
- Medan wajib yang hilang (
required); - Jenis salah, contohnya rentetan di tempat yang dijangka nombor (
type); - Nilai di luar julat (
minimum/maximum/minLength); - Nilai di luar set yang dibenarkan (
enum); - Jenis elemen tatasusunan tidak konsisten atau panjang tatasusunan salah (
items/minItems); - Medan yang hilang dalam struktur bersarang (pengesahan rekursif melalui
properties); - Medan tambahan (
additionalProperties: false).
Apa yang tidak boleh diselesaikannya: peraturan perniagaan ("masa tamat mesti lebih lewat daripada masa mula"), keadaan merentas medan (sesetengah boleh dinyatakan dengan if/then,
tetapi kebolehbacaan terjejas), keunikan pangkalan data, dan semakan kebenaran.
Jadi Schema ialah penjaga pintu untuk "bentuk data", bukan pengganti logik perniagaan.
Patutkah saya menggunakan draft-07 atau 2020-12?
Gunakan 2020-12 untuk projek baharu; untuk sistem sedia ada, semak apa yang disokong oleh pengesah.
| Versi | Status | Perubahan utama |
|---|---|---|
| draft-04 | Lapuk | Banyak pustaka lama masih menggunakan sebagai lalai |
| draft-07 | Standard de facto | Sokongan paling luas, ekosistem paling kaya |
| 2019-09 | Peralihan | Memperkenalkan $defs, unevaluatedProperties |
| 2020-12 | Disyorkan sekarang | items dipecahkan kepada prefixItems + items, menyokong $dynamicRef |
Dua perangkap migrasi:
- Sintaks tuple berubah: dalam draft-07,
itemsbernilai tatasusunan menyatakan tuple; 2020-12 memindahkannya keprefixItems; $refdan kata kunci bersebelahan: dalam draft-07 kata kunci bersebelahan diabaikan apabila$refhadir; 2020-12 membenarkan mereka wujud bersama.
Jika pengesah hanya menyokong draft-07, menulis sintaks 2020-12 tetap akan diabaikan secara senyap — dan kes paling berbahaya ialah "pengesahan lulus" apabila tiada apa yang disahkan.
Bagaimana saya menjana jenis TypeScript daripada Schema?
Dua laluan: terbitkan jenis daripada Schema, atau jana Schema daripada jenis.
Daripada Schema kepada jenis (backend mentakrifkan kontrak, frontend menggunakannya):
import type { FromSchema } from 'json-schema-to-ts';
const userSchema = {
type: 'object',
properties: {
id: { type: 'string' },
age: { type: 'integer' },
},
required: ['id'],
additionalProperties: false,
} as const;
type User = FromSchema<typeof userSchema>;
// { id: string; age?: number }
Kuncinya ialah as const: tanpanya, type: 'string' disimpulkan sebagai string dan bukannya literal 'string', dan penyimpulan menjadi rosak.
Daripada jenis kepada Schema (frontend sudah mempunyai jenis dan memerlukan pengesahan masa jalan):
import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';
const userSchema = z.object({ id: z.string(), age: z.number().int().optional() });
const jsonSchema = zodToJsonSchema(userSchema);
Kelebihan laluan ini ialah "satu definisi menghasilkan kedua-dua jenis dan pengesahan masa jalan"; kosnya ialah memperkenalkan DSL,
dan Schema yang dijana mungkin membawa butiran pelaksanaan (contohnya struktur $ref yang tidak menarik).
Bagaimana saya menemui medan tertentu apabila pengesahan gagal?
Lihat instancePath (2020-12) atau dataPath (draft-07) yang dikembalikan oleh pengesah; ia ialah JSON Pointer kepada lokasi ralat.
[
{
"instancePath": "/items/2/price",
"schemaPath": "#/properties/items/items/properties/price/type",
"keyword": "type",
"message": "must be number"
}
]
instancePath: "/items/2/price" memberitahu anda secara langsung: medan price elemen ketiga dalam tatasusunan items mempunyai jenis yang salah.
Dua cadangan praktikal:
- Kembalikan semua ralat serentak dan bukannya melontar pada ralat pertama — frontend yang membetulkan semuanya dalam satu pusingan lebih pantas daripada percubaan berulang-alik;
- Tukar Pointer kepada bahasa biasa sebelum menunjukkannya kepada pengguna akhir;
/items/2/priceberguna untuk pembangun dan tidak berguna untuk pengguna.
allErrors: true ialah suis dalam kebanyakan pengesah (Ajv memerlukannya dihidupkan secara eksplisit); lalai selalunya mod gagal-pantas.
Patutkah additionalProperties: false dihidupkan?
Hidupkan untuk kontrak dalaman; berhati-hati dengan API awam.
Sebaik dihidupkan, mana-mana medan yang tidak diisytiharkan di bawah properties gagal pengesahan. Faedahnya ialah menangkap kesilapan taip
(userNmae) dan penghantaran tidak dijangka; keburukannya ialah penambahan medan huluan merosakkan pengguna hiliran, memusnahkan keserasian ke belakang.
Jalan tengah:
| Senario | Cadangan |
|---|---|
| Antara perkhidmatan dalaman | Hidupkan. Perubahan medan melalui pengurusan versi |
| API luaran awam | Matikan. Benarkan huluan menambah medan |
| Menggunakan data pihak ketiga | Matikan. Anda tidak mengawal pihak lain |
| Pengesahan konfigurasi | Hidupkan. Kesilapan taip dalam konfigurasi harus gagal serta-merta |
Bagaimana saya mengesahkan bahawa elemen tatasusunan adalah unik?
Dua semantik berbeza — jangan kelirukan:
{ "uniqueItems": true }
Ini menghapuskan pendua seluruh elemen, memerlukan tatasusunan tidak mengandungi dua elemen yang sama dalam.
{
"type": "array",
"items": { "type": "object", "properties": { "id": { "type": "string" } } },
"uniqueItems": true
}
Coretan di atas hanya menjamin objek sebagai satu keseluruhan tidak diduplikasi; {"id":"a","name":"x"} dan {"id":"a","name":"y"} tidak dikira sebagai pendua.
Untuk menghapuskan pendua mengikut medan tertentu, Schema tidak boleh menyatakannya; anda perlu mengesahkan dalam lapisan perniagaan:
const ids = items.map((item) => item.id);
if (new Set(ids).size !== ids.length) {
throw new Error('id 重复');
}
Adakah format benar-benar mengesahkan e-mel dan tarikh?
Tidak semestinya. Dalam spesifikasi, format ialah "anotasi" secara lalai dan bukannya "penegasan", jadi pengesah boleh mengumpulnya tanpa mengesahkannya.
- Ajv memerlukan pemasangan dan konfigurasi pakej
ajv-formatsyang berasingan, jika tidakformat: "email"tidak berkesan; - Sesetengah pengesah memperlakukan
formatsebagai penegasan yang dihidupkan secara lalai, jadi tingkah laku tidak konsisten; - Walaupun dihidupkan, regex
emailsangat longgar —a@blulus.
Kesimpulan: jangan anggap format sebagai pengesahan yang kuat. Lakukan pengesahan ketat untuk e-mel, URL dan tarikh dalam kod perniagaan anda;
mengekalkan format dalam Schema sebagai dokumentasi adalah berbaloi, tetapi jangan harap ia menghentikan data kotor.
3. Rujukan pantas
| Soalan | Kesimpulan |
|---|---|
| Cari nod mengikut keadaan | JSONPath |
| Penanganan tepat, sapukan tampalan | JSON Pointer |
| Ubah elemen tatasusunan | JSON Patch, Merge Patch tidak boleh melakukannya |
| Gabungkan konfigurasi | Merge Patch |
| Versi Schema untuk projek baharu | 2020-12, perhatikan prefixItems |
| Jana jenis TS daripada Schema | json-schema-to-ts + as const |
| Menemui ralat pengesahan | Baca instancePath, hidupkan allErrors |
| Hapuskan pendua mengikut medan | Schema tidak boleh; gunakan Set dalam lapisan perniagaan |
format: email |
Kebanyakan pengesah tidak menyemaknya secara lalai, jadi jangan bergantung padanya |