JSON Toolkit

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.

Lanjutan~10 min bacaanDiterbitkan 2026-10-31Dikemas kini 2026-10-31
Gambar rajah penentuan lokasi ralat pengesahan Ajv dan tadbir urus mesej

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:

  • instancePath ialah JSON Pointer RFC 6901. Rentetan kosong bermaksud akar; /items/0/price bermaksud medan harga elemen tatasusunan pertama.
  • schemaPath bermula dengan # dan menunjuk kepada kata kunci di dalam dokumen Schema. Merentas $ref, ia menunjuk di dalam Schema yang dirujuk, jadi anda perlukan $id untuk mengetahui dari fail mana ia datang.
  • Kunci yang mengandungi / atau ~ dilepaskan kepada ~1 dan ~0 masing-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:

  1. 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.
  2. Serahkan bentuk jamak dan penjodoh bilangan kepada pustaka i18n. Jangan sekali-kali mengekod keras "characters" dalam templat; 1 character dan 3 characters berbeza.
  3. Terjemahkan label medan juga. params.missingProperty ialah email, 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.