Apabila Ralat JSON Schema Tidak Masuk Akal: Penentuan Lokasi dan Tadbir Urus Mesej Ralat Ajv
Inti pati: Pengesah mengeluarkan ralat mesin; pengguna ingin tahu kotak input mana yang salah. Merangkumi tatasusunan errors Ajv, instancePath berbanding schemaPath, pengelompokan dan penggunaan perkataan.

Intinya: baca instancePath dahulu untuk mencari data yang gagal, kemudian schemaPath untuk mencari peraturan yang dicetuskan; untuk required dan additionalProperties, ambil nama kunci sebenar daripada params sebelum memilih mesej.
Tugas pengesah ialah menyatakan peraturan mana yang gagal. Menterjemahkannya menjadi "kotak input ini salah" ialah tugas anda.
1. Apa yang sebenarnya diberikan oleh tatasusunan errors kepada anda
Ajv tidak membuang ralat pada kegagalan pengesahan; ia meninggalkan hasil dalam validate.errors. Konfigurasi lalai menyimpan hanya ralat pertama, dan allErrors yang menghasilkan senarai penuh. Satu objek ralat mempunyai lima medan yang penting, dan memahaminya ialah prasyarat untuk segala-galanya di hilir:
| Medan | Maksud | Contoh |
|---|---|---|
instancePath |
JSON Pointer kepada nilai yang gagal | /email |
schemaPath |
Lokasi kata kunci yang gagal dalam Schema | #/properties/email/format |
keyword |
Nama kekangan yang dilanggar | format |
params |
Parameter kekangan itu yang boleh dibaca mesin | { "format": "email" } |
message |
Prosa bahasa Inggeris yang berubah antara versi | must match format "email" |
Ralat sebenar kelihatan seperti ini:
[
{
"instancePath": "/email",
"schemaPath": "#/properties/email/format",
"keyword": "format",
"params": { "format": "email" },
"message": "must match format \"email\""
}
]
Perhatikan bahawa message ialah prosa yang ditulis untuk manusia, bukan antara muka yang stabil. Ajv mungkin menukar perkataannya antara versi, dan ia tidak dapat menyokong produk yang disetempatkan. Jangan sekali-kali menghuraikannya dengan ungkapan biasa, dan jangan sekali-kali memulangkannya secara verbatim kepada klien.
2. instancePath dan schemaPath menjawab soalan yang berbeza
instancePath menyatakan bahagian data mana yang gagal; schemaPath menyatakan peraturan mana yang dicetuskan. Mereka ialah dua sistem koordinat, dan mereka tidak simetri:
instancePathialah JSON Pointer RFC 6901. Rentetan kosong bermaksud akar;/items/0/pricebermaksud medan harga elemen tatasusunan pertama.schemaPathbermula dengan#dan menunjuk kepada kata kunci di dalam dokumen Schema. Merentas$ref, ia menunjuk di dalam Schema yang dirujuk, jadi anda perlukan$iduntuk mengetahui dari fail mana ia datang.- Kunci yang mengandungi
/atau~dilepaskan kepada~1dan~0masing-masing. Batalkan itu sebelum paparan, atau UI akan menunjukkan simbol yang mengelirukan.
Sebagai contoh, instancePath: "/email" bersama schemaPath: "#/properties/email/format" bermaksud medan e-mel pada objek akar tidak lulus semakan format.
Perangkap paling biasa ialah required dan additionalProperties: instancePath mereka menunjuk kepada objek induk, bukan kepada kunci yang melanggar.
| Kata kunci | instancePath menunjuk kepada |
Di mana kunci sebenar berada |
|---|---|---|
required |
Objek induk (rentetan kosong pada akar) | params.missingProperty |
additionalProperties |
Objek induk | params.additionalProperty |
type |
Nilai yang gagal itu sendiri | — |
enum |
Nilai yang gagal itu sendiri | params.allowedValues |
Apabila memetakan ralat ke medan borang, tangani dua kes ini dahulu. Jika tidak "e-mel tiada" menjadi "ralat objek akar", dan pengguna tidak tahu di mana perlu menaip.
3. allErrors dan verbose: kelengkapan berbanding kos
Lalai allErrors: false menjadikan Ajv berhenti pada kegagalan pertama. Itu baik untuk alat baris arahan dan merosakkan untuk borang: pengguna membaiki satu medan, menghantar, melihat ralat seterusnya, dan mengulangi tiga kali sebelum borang lulus.
| Tetapan | Tingkah laku | Faedah | Kos |
|---|---|---|---|
allErrors: false |
Hanya ralat pertama | Paling pantas, memori paling kecil | Penghantaran berulang |
allErrors: true |
Kumpul setiap ralat | Semua masalah dalam satu pas | Bilangan ralat meledak pada Schema kompleks |
verbose: true |
Lampirkan schema dan data pada setiap ralat |
Diagnostik pelayan lebih mudah | Saiz meningkat sangat; jangan sekali-kali dedahkannya |
messages: false |
Gugurkan medan message |
Muatan lebih kecil | Hilang petunjuk penyahpepijatan bahasa Inggeris |
Pemisahan yang disyorkan: aktifkan allErrors: true untuk pengesahan pelayan dan simpan tatasusunan penuh dalam log dalaman, manakala respons awam hanya membawa medan yang disenarai putih. Hidupkan verbose hanya semasa menyiasat; ia membenamkan serpihan Schema yang sepadan dan data pengguna ke dalam setiap ralat, yang memakan memori dan boleh mendedahkan nilai sensitif ke dalam log.
Jumlah ralat ialah masalah biasa yang lain. Cabang anyOf yang gagal mengeluarkan beberapa ralat anak ditambah satu ralat ringkasan, dan oneOf dengan banyak cabang lebih teruk. Tunjukkan tatasusunan seperti adanya dan pengguna melihat lima mesej hampir serupa untuk satu medan. Pengelompokan mesti berlaku sebelum paparan.
4. Kelompokkan ralat mengikut medan
Pengelompokan melakukan tiga perkara: tukar laluan kembali kepada nama medan, betulkan anjakan required dan additionalProperties, dan gabungkan berbilang ralat pada medan yang sama menjadi satu mesej yang paling boleh diambil tindakan.
/** One validation error ready for display */
interface FieldError {
field: string;
keyword: string;
params: Record<string, unknown>;
}
/**
* Group Ajv errors by field.
* @param errors Error array produced by Ajv
* @returns Map keyed by field path, each value listing its errors
*/
function groupByField(errors: Array<Record<string, any>>): Record<string, FieldError[]> {
const grouped: Record<string, FieldError[]> = {};
for (const errorItem of errors) {
const keyword: string = errorItem.keyword;
let field: string = errorItem.instancePath;
if (keyword === 'required') {
const parentPath: string = errorItem.instancePath;
field = `${parentPath}/${errorItem.params.missingProperty}`;
} else if (keyword === 'additionalProperties') {
field = `${errorItem.instancePath}/${errorItem.params.additionalProperty}`;
}
const normalizedField = field === '' ? '(root)' : field;
(grouped[normalizedField] ??= []).push({
field: normalizedField,
keyword,
params: errorItem.params ?? {},
});
}
return grouped;
}
Gabungkan dengan susunan keutamaan: required (tiada) dahulu, kemudian type, kemudian enum/const, kemudian format/pattern, kemudian panjang dan julat. Tunjukkan hanya mesej keutamaan tertinggi bagi setiap medan dan simpan selebihnya dalam log. Alasannya mudah: apabila jenis salah, mesej panjang ialah bunyi bising, dan membaiki jenis ialah satu-satunya langkah seterusnya yang munasabah.
Ralat ringkasan untuk oneOf dan anyOf biasanya tidak membawa maklumat yang boleh diambil tindakan. Langkau mereka semasa paparan dan simpan hanya ralat cabang yang menunjuk kepada medan konkrit.
5. Mesej tersuai: laluan pemalam dan laluan templat
Ajv tidak menyertakan perkataan yang disetempatkan, jadi terdapat dua pilihan praktikal.
Pilihan satu: pemalam ajv-errors. Ia menambah kata kunci errorMessage supaya mesej berada bersebelahan dengan kekangan:
{
"properties": {
"email": {
"type": "string",
"format": "email",
"errorMessage": {
"format": "Please enter a valid email address",
"type": "Email must be a string"
}
}
}
}
Kelebihannya ialah perkataan bergerak bersama medan dan Schema berfungsi sekali sebagai dokumentasi. Kelemahannya ialah teks itu terkunci dalam satu bahasa, jadi penyetempatan bermaksud menyelenggara beberapa Schema. Pemalam juga hanya menggantikan setiap mesej apabila allErrors: true ditetapkan; lupakan bendera itu dan anda mendapat campuran mesej tersuai dan bahasa Inggeris lalai.
Pilihan dua: jadual kata kunci-ke-templat. Pengesah hanya membekalkan keyword dan params; aplikasi menghasilkan perkataannya:
| Kata kunci | Templat | Parameter tersedia |
|---|---|---|
required |
Medan wajib tiada: {field} | missingProperty |
type |
Jenis dijangka: {type} | type |
minLength |
Masukkan sekurang-kurangnya {limit} aksara | limit |
maximum |
Tidak boleh melebihi {limit} | limit |
format |
Format tidak sah: {format} | format |
enum |
Mesti salah satu pilihan yang dibenarkan | allowedValues |
pattern |
Nilai tidak sepadan dengan format yang dijangka | pattern (jangan gaungkannya) |
Pilihan dua sesuai untuk produk yang memerlukan berbilang bahasa dan nada yang konsisten. Ia memisahkan pengesahan daripada persembahan, menjauhkan terjemahan daripada Schema, dan membolehkan frontend menggunakan semula templat yang sama.
6. Penyetempatan: mesej bukan milik pengesah
Memindahkan perkataan ke pek bahasa ialah titik permulaan tadbir urus mesej. Pek bahasa menyimpan templat dan kunci, bukan nama medan; label medan datang daripada konfigurasi borang atau kontrak API, dan kedua-duanya digabungkan pada masa paparan.
/** Validation message templates keyed by Ajv keyword */
const VALIDATION_TEMPLATES: Record<string, (params: Record<string, any>) => string> = {
required: (params) => `Missing required field: ${params.missingProperty}`,
type: (params) => `Expected type: ${params.type}`,
minLength: (params) => `Enter at least ${params.limit} characters`,
maximum: (params) => `Must not exceed ${params.limit}`,
format: () => 'Invalid format',
};
/**
* Build one user-facing validation message.
* @param keyword Name of the failed constraint
* @param params Constraint parameters
* @returns Localized message text
*/
function buildMessage(keyword: string, params: Record<string, any>): string {
const template = VALIDATION_TEMPLATES[keyword];
return template ? template(params) : 'The value is invalid';
}
Tiga peraturan penting di sini:
- Jangan gabungkan ayat. "Email format is invalid" dan padanan Cina/Inggerisnya tidak berkongsi susunan perkataan, dan pemasangan rentetan akhirnya akan menghasilkan omong kosong dalam salah satu daripadanya.
- Serahkan bentuk jamak dan penjodoh bilangan kepada pustaka i18n. Jangan sekali-kali mengekod keras "characters" dalam templat;
1 characterdan3 charactersberbeza. - Terjemahkan label medan juga.
params.missingPropertyialahemail, sedangkan UI sepatutnya menyatakan "Alamat e-mel". Simpan pemetaan itu dalam konfigurasi borang, bukan dalam pek bahasa.
7. Jangan pulangkan struktur dalaman kepada klien
Memulangkan schemaPath, params, dan message bersama adalah mudah semasa pembangunan dan kebocoran maklumat dalam pengeluaran. allowedValues mendedahkan enum dalaman, pattern mendedahkan ungkapan biasa pengesahan, dan schemaPath mendedahkan susun atur fail dan penamaan medan — dalam sesetengah kes memberikan penyerang peta titik lemah anda.
Respons awam menyimpan tepat tiga perkara: laluan medan (atau nama medan borang), kod ralat yang stabil, dan teks untuk pengguna. Gunakan kod pendek yang stabil dan bukannya keyword mentah supaya anda mengekalkan ruang untuk menukar pengesah kemudian.
{
"type": "about:blank",
"title": "Request validation failed",
"status": 422,
"errors": [
{ "field": "email", "code": "invalid_format", "message": "Please enter a valid email address" },
{ "field": "items[0].price", "code": "invalid_pattern", "message": "Amount must have exactly two decimals" }
]
}
Log pelayan sepatutnya menyimpan sebaliknya: tatasusunan ralat penuh, id permintaan, dan versi Schema, supaya siasatan dapat menjawab "peraturan mana yang menghalang ini". Elakkan membuang keseluruhan badan permintaan ke dalam log; simpan laluan dan kata kunci yang gagal sebaliknya.
8. Persembahan frontend: daripada sorotan medan kepada penanda editor
Ralat akhirnya mendarat dalam UI, dan persembahan bergantung pada konteks:
| Konteks | Persembahan | Tindakan utama |
|---|---|---|
| Borang | Teks merah di bawah medan, fokuskan ralat pertama | Petakan laluan kepada nama borang; kekalkan indeks tatasusunan |
| Import jadual pukal | Nombor baris, nama lajur, ringkasan, boleh dieksport | Tukar /items/3/price kepada baris 4, lajur harga |
| Editor JSON | Penanda sebaris dan panel masalah | Gunakan penghurai untuk memetakan JSON Pointer kepada baris dan lajur |
| Alat penyahpepijat API | Senarai ralat berstruktur yang mengekalkan schemaPath |
Untuk pembangun, jadi lebih banyak butiran boleh diterima |
Tiga nota pelaksanaan:
- Tunjukkan hanya ralat pertama bagi setiap medan, dan runtuhkan selebihnya menjadi "2 lagi isu", supaya borang tidak lemas dalam merah.
- Kekalkan indeks tatasusunan untuk sasaran tetapi gugurkannya dalam label. "Baris 4, harga" lebih mudah dibaca daripada
/items/3/price. - Senario editor memerlukan pemetaan kedudukan. Carian rentetan biasa tidak boleh dipercayai apabila nama medan berulang; penghurai sepatutnya menyediakan offset bagi setiap nod, yang kemudian anda tukar kepada baris dan lajur.
Jika JSON datang daripada pengguna yang menaip dan bukannya borang, sahkan dokumen itu sah secara sintaksis dahulu. Ralat sintaks berlaku semasa penghuraian dan tidak pernah sampai ke pengesahan Schema. Hurai & Format JSON melaporkan baris dan lajur tepat, yang jauh lebih pantas daripada mengejar ralat Schema. Untuk cara Schema yang terlalu ketat muncul apabila ia disimpulkan daripada contoh, lihat Mengapa JSON Schema Dijana Automatik Terus Menolak Data Sebenar; untuk konfigurasi pengesah dan sistem kata kunci, lihat JSON Schema: Daripada Asas kepada Pengeluaran.
9. Soalan yang sering ditanya
Mengapa saya hanya mendapat satu ralat?
allErrors ialah false secara lalai, jadi Ajv memulangkan pada kegagalan pertama. Hidupkannya secara eksplisit untuk pengesahan borang; jika tidak pengguna mesti menghantar berulang kali untuk menemui setiap masalah.
Mengapa laluan pada ralat required kosong?
instancePath untuk required menunjuk kepada objek induk, iaitu rentetan kosong pada akar; nama kunci yang tiada berada dalam params.missingProperty. Gabungkan keduanya untuk mendapatkan laluan penuh. Menunjukkan instancePath sahaja menghasilkan "ralat objek akar" yang tidak berguna.
Bolehkah saya menunjukkan message terus kepada pengguna?
Tidak digalakkan. Ia ialah prosa bahasa Inggeris yang perkataannya berubah antara versi, dan ia tidak mempunyai keupayaan penyetempatan. Hasilkan teks daripada keyword dan params sebaliknya dan simpan message untuk log.
Mengapa mesej ralat tersuai saya diabaikan?
Semak dahulu bahawa allErrors: true didayakan; ajv-errors hanya boleh menggantikan semua mesej dengan bendera itu. Kemudian sahkan errorMessage berada pada tahap yang sama dengan kata kunci yang gagal — satu tahap tersasar dan ia tidak akan dikenali.
Medan mana yang selamat untuk dipulangkan dalam pengeluaran?
Laluan medan, kod ralat yang stabil, dan teks untuk pengguna. Simpan schemaPath, params, dan message dalam log pelayan: itu menyokong penyahpepijatan tanpa mendedahkan enum, ungkapan biasa, atau susun atur fail dalaman.