JSON Toolkit

जब JSON Schema Errors समझ में न आएँ: Ajv Error Location और Message Governance

मुख्य बात: Validator machine errors देता है; उपयोगकर्ता जानना चाहता है कि कौन-सा input box गलत है। इसमें Ajv errors array, instancePath बनाम schemaPath, grouping और wording शामिल हैं।

उन्नत~10 मिनट पढ़ें2026-10-31 को प्रकाशित2026-10-31 को अपडेट किया गया
Ajv validation error location और message governance का आरेख

सारांश: विफल डेटा locate करने के लिए पहले instancePath पढ़ें, फिर जो नियम चला उसे locate करने के लिए schemaPath; required और additionalProperties के लिए message चुनने से पहले params से असली key नाम निकालें।

Validator का काम यह बताना है कि कौन-सा नियम विफल हुआ। उसे "यह input box गलत है" में बदलना आपका काम है।

1. errors array वास्तव में आपको क्या देता है

Ajv validation विफलता पर throw नहीं करता; यह परिणाम validate.errors में छोड़ देता है। डिफ़ॉल्ट configuration केवल पहला error रखती है, और पूरी सूची allErrors से बनती है। एक error object में पाँच महत्वपूर्ण फ़ील्ड होते हैं, और इन्हें समझना आगे की हर चीज़ की पूर्व-शर्त है:

फ़ील्ड अर्थ उदाहरण
instancePath विफल मान की ओर JSON Pointer /email
schemaPath schema में विफल keyword की स्थिति #/properties/email/format
keyword जिस बाधा का उल्लंघन हुआ उसका नाम format
params उस बाधा के machine-readable parameters { "format": "email" }
message अंग्रेज़ी गद्य जो versions के बीच बदलता है must match format "email"

एक वास्तविक error ऐसा दिखता है:

[
  {
    "instancePath": "/email",
    "schemaPath": "#/properties/email/format",
    "keyword": "format",
    "params": { "format": "email" },
    "message": "must match format \"email\""
  }
]

ध्यान दें कि message मनुष्यों के लिए लिखा गया गद्य है, स्थिर interface नहीं। Ajv इसे versions के बीच बदल सकता है, और यह किसी localized product के काम नहीं आ सकता। इसे कभी regular expression से parse न करें, और इसे कभी ज्यों का त्यों client को वापस न लौटाएँ।

2. instancePath और schemaPath अलग सवालों के जवाब देते हैं

instancePath बताता है कि डेटा का कौन-सा हिस्सा विफल हुआ; schemaPath बताता है कि कौन-सा नियम चला। ये दो निर्देशांक प्रणालियाँ हैं, और ये सममित नहीं हैं:

  • instancePath एक RFC 6901 JSON Pointer है। खाली स्ट्रिंग का अर्थ root है; /items/0/price का अर्थ पहले array element का price फ़ील्ड है।
  • schemaPath # से शुरू होता है और schema दस्तावेज़ के अंदर keyword की ओर इशारा करता है। किसी $ref के पार यह संदर्भित schema के अंदर इशारा करता है, इसलिए यह जानने के लिए कि वह किस फ़ाइल से आया है, आपको $id चाहिए।
  • / या ~ वाली key क्रमशः ~1 और ~0 में escape होती है। दिखाने से पहले इसे पलटें, वरना UI में उलझाने वाले चिह्न दिखेंगे।

उदाहरण के लिए, instancePath: "/email" के साथ schemaPath: "#/properties/email/format" का अर्थ है कि root object का email फ़ील्ड format जाँच में पास नहीं हुआ।

सबसे आम जाल required और additionalProperties हैं: इनका instancePath मूल object की ओर इशारा करता है, गलत key की ओर नहीं।

Keyword instancePath किस ओर इशारा करता है असली key कहाँ रहती है
required मूल object (root पर खाली स्ट्रिंग) params.missingProperty
additionalProperties मूल object params.additionalProperty
type विफल मान स्वयं —
enum विफल मान स्वयं params.allowedValues

Form फ़ील्ड पर errors map करते समय पहले इन दो स्थितियों को संभालें। वरना "email गायब है" बन जाता है "root object में error", और उपयोगकर्ता को पता ही नहीं कि कहाँ टाइप करना है।

3. allErrors और verbose: पूर्णता बनाम लागत

डिफ़ॉल्ट allErrors: false Ajv को पहली विफलता पर रोक देता है। यह command-line उपकरण के लिए ठीक है और form के लिए विनाशकारी: उपयोगकर्ता एक फ़ील्ड ठीक करता है, submit करता है, अगला error देखता है, और form पास होने से पहले तीन बार दोहराता है।

सेटिंग व्यवहार लाभ लागत
allErrors: false केवल पहला error सबसे तेज़, कम memory बार-बार submissions
allErrors: true हर error इकट्ठा करें एक ही बार में सारी समस्याएँ जटिल schemas पर error संख्या फूल जाती है
verbose: true हर error पर schema और data जोड़ें server-side निदान आसान आकार बहुत बढ़ता है; इसे कभी उजागर न करें
messages: false message फ़ील्ड हटाएँ छोटे payloads अंग्रेज़ी debugging सुराग खो जाता है

सुझाया विभाजन: server validation के लिए allErrors: true चालू करें और पूरा array आंतरिक logs में रखें, जबकि सार्वजनिक response केवल whitelisted फ़ील्ड ले। verbose केवल जाँच के दौरान चालू करें; यह हर error में matched schema fragment और उपयोगकर्ता का डेटा embed कर देता है, जो memory महँगी बनाता है और संवेदनशील मान logs में लीक कर सकता है।

Error मात्रा दूसरी आम समस्या है। विफल anyOf branch कई child errors के साथ एक सारांश error देता है, और कई branches वाला oneOf उससे भी बुरा है। Array ज्यों का त्यों दिखाएँ तो उपयोगकर्ता एक फ़ील्ड के लिए पाँच लगभग-समान संदेश देखता है। Rendering से पहले grouping करनी ही होगी।

4. Errors को फ़ील्ड के अनुसार group करें

Grouping तीन काम करती है: path को वापस फ़ील्ड नाम में बदलना, required और additionalProperties का offset सुधारना, और एक ही फ़ील्ड के कई errors को सबसे कारगर एक संदेश में मिलाना।

/** प्रदर्शन के लिए तैयार एक validation error */
interface FieldError {
  field: string;
  keyword: string;
  params: Record<string, unknown>;
}

/**
 * Ajv errors को फ़ील्ड के अनुसार group करें।
 * @param errors Ajv द्वारा उत्पन्न error array
 * @returns फ़ील्ड path से keyed Map, हर मान में उसके 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;
}

प्राथमिकता क्रम के साथ merge करें: पहले required (गायब), फिर type, फिर enum/const, फिर format/pattern, फिर लंबाई और सीमा। हर फ़ील्ड पर केवल सबसे उच्च-प्राथमिकता संदेश दिखाएँ और बाकी logs में रखें। कारण सीधा है: जब type गलत है, तो लंबाई का संदेश शोर है, और type ठीक करना ही एकमात्र समझदार अगला कदम है।

oneOf और anyOf के सारांश errors सामान्यतः कोई कारगर जानकारी नहीं रखते। Rendering के दौरान उन्हें छोड़ें और केवल उन branch errors को रखें जो ठोस फ़ील्ड की ओर इशारा करते हैं।

5. कस्टम संदेश: plugin रास्ता और template रास्ता

Ajv localized wording नहीं देता, इसलिए दो व्यावहारिक विकल्प हैं।

पहला विकल्प: ajv-errors plugin। यह errorMessage keyword जोड़ता है ताकि संदेश बाधाओं के पास ही रहें:

{
  "properties": {
    "email": {
      "type": "string",
      "format": "email",
      "errorMessage": {
        "format": "Please enter a valid email address",
        "type": "Email must be a string"
      }
    }
  }
}

लाभ यह है कि wording फ़ील्ड के साथ चलती है और schema दस्तावेज़ का भी काम करता है। कमी यह है कि पाठ एक ही भाषा में जम जाता है, इसलिए localization का अर्थ है कई schemas बनाए रखना। Plugin तब भी हर संदेश केवल तब बदलता है जब allErrors: true सेट हो; वह flag भूलें और आपको कस्टम और डिफ़ॉल्ट अंग्रेज़ी का मिश्रण मिलेगा।

दूसरा विकल्प: keyword-to-template तालिका। Validator केवल keyword और params देता है; wording application बनाता है:

Keyword Template उपलब्ध parameters
required Missing required field: {field} missingProperty
type Expected type: {type} type
minLength Enter at least {limit} characters limit
maximum Must not exceed {limit} limit
format Invalid format: {format} format
enum Must be one of the allowed options allowedValues
pattern Value does not match the expected format pattern (इसे echo न करें)

दूसरा विकल्प उन products के लिए उपयुक्त है जिन्हें कई भाषाएँ और एक सुसंगत शैली चाहिए। यह validation को presentation से अलग करता है, अनुवाद को schema से बाहर रखता है, और frontend को वही templates दोबारा उपयोग करने देता है।

6. Localization: संदेश validator के नहीं होते

Wording को language packs में ले जाना message governance की शुरुआत है। Language pack templates और keys रखता है, कभी फ़ील्ड नाम नहीं; फ़ील्ड labels form configuration या API contract से आते हैं, और render time पर दोनों जोड़े जाते हैं।

/** Ajv keyword से keyed validation message templates */
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',
};

/**
 * एक उपयोगकर्ता-मुखी validation संदेश बनाएँ।
 * @param keyword विफल बाधा का नाम
 * @param params बाधा parameters
 * @returns Localized संदेश पाठ
 */
function buildMessage(keyword: string, params: Record<string, any>): string {
  const template = VALIDATION_TEMPLATES[keyword];
  return template ? template(params) : 'The value is invalid';
}

यहाँ तीन नियम महत्वपूर्ण हैं:

  1. वाक्यों को जोड़ें नहीं। "Email format is invalid" और उसके चीनी समकक्ष का शब्द क्रम समान नहीं है, और string assembly अंततः किसी एक में बेतुका पाठ बना देगी।
  2. Plurals और measure words i18n library को सौंपें। Template में कभी "characters" hardcode न करें; 1 character और 3 characters अलग हैं।
  3. फ़ील्ड labels का भी अनुवाद करें। params.missingProperty email है, जबकि UI को "Email address" कहना चाहिए। यह mapping form configuration में रखें, language pack में नहीं।

7. आंतरिक संरचना clients को वापस न लौटाएँ

schemaPath, params और message एक साथ लौटाना विकास के दौरान सुविधाजनक और प्रोडक्शन में जानकारी का लीक है। allowedValues आंतरिक enums उजागर करता है, pattern validation regexes उजागर करता है, और schemaPath फ़ाइल layout और फ़ील्ड naming उजागर करता है — कुछ मामलों में हमलावरों को आपकी कमज़ोरियों का नक्शा सौंप देता है।

सार्वजनिक response में ठीक तीन चीज़ें रहती हैं: फ़ील्ड path (या form फ़ील्ड नाम), स्थिर error code, और उपयोगकर्ता-मुखी पाठ। कच्चे keyword के बजाय छोटे स्थिर codes इस्तेमाल करें ताकि आगे validator बदलने की गुंजाइश बनी रहे।

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

Server logs में इसका उल्टा होना चाहिए: पूरा error array, request id और schema version, ताकि जाँच यह जवाब दे सके "किस नियम ने इसे रोका"। पूरे request bodies logs में dump करने से बचें; इसके बजाय विफल paths और keywords रखें।

8. Frontend प्रस्तुति: फ़ील्ड highlight से editor markers तक

Errors अंततः UI में पहुँचते हैं, और प्रस्तुति संदर्भ पर निर्भर है:

संदर्भ प्रस्तुति मुख्य क्रिया
Form फ़ील्ड के नीचे लाल पाठ, पहले error पर focus Paths को form नामों में map करें; array indices रखें
Bulk table import Row number, column नाम, सारांश, exportable /items/3/price को row 4, price column बनाएँ
JSON editor Inline markers और problems panel JSON Pointer को line और column में map करने के लिए parser इस्तेमाल करें
API debugging tool schemaPath रखने वाली संरचित error सूची Developer-facing, इसलिए अधिक विवरण स्वीकार्य है

तीन implementation टिप्पणियाँ:

  • हर फ़ील्ड पर केवल पहला error दिखाएँ, बाकी को "2 more issues" में समेटें, ताकि form लाल रंग में न डूबे।
  • Targeting के लिए array indices रखें पर labels में हटा दें। "Row 4, price" /items/3/price से बेहतर पढ़ा जाता है।
  • Editor परिदृश्यों में position mapping चाहिए। फ़ील्ड नाम दोहराने पर साधारण string search अविश्वसनीय है; parser को हर node के offsets देने चाहिए, जिन्हें आप फिर line और column में बदलें।

अगर JSON form के बजाय उपयोगकर्ता के टाइप करने से आता है, तो पहले पुष्टि करें कि दस्तावेज़ वाक्यात्मक रूप से मान्य है। Syntax errors parsing के दौरान होते हैं और schema validation तक कभी नहीं पहुँचते। JSON Parse और Format ठीक line और column बताता है, जो schema errors का पीछा करने से कहीं तेज़ है। नमूनों से अनुमान लगाते समय अति-सख्त schemas कैसे बनते हैं, इसके लिए Auto-Generated JSON Schema असली डेटा क्यों अस्वीकार करता रहता है देखें; validator configuration और keyword प्रणाली के लिए JSON Schema: मूल बातों से प्रोडक्शन तक देखें।

9. अक्सर पूछे जाने वाले प्रश्न

मुझे केवल एक error क्यों मिलता है?

allErrors डिफ़ॉल्ट रूप से false है, इसलिए Ajv पहली विफलता पर लौट आता है। Form validation के लिए इसे स्पष्ट रूप से चालू करें; वरना उपयोगकर्ता को हर समस्या खोजने के लिए बार-बार submit करना पड़ेगा।

required error पर path खाली क्यों है?

required के लिए instancePath मूल object की ओर इशारा करता है, जो root पर खाली स्ट्रिंग है; गायब key का नाम params.missingProperty में बैठा है। पूरा path पाने के लिए दोनों जोड़ें। केवल instancePath दिखाने से बेकार "root object error" मिलता है।

क्या मैं message सीधे उपयोगकर्ताओं को दिखा सकता हूँ?

सलाहनीय नहीं। यह अंग्रेज़ी गद्य है जिसकी wording versions के बीच बदलती है, और इसमें localization क्षमता नहीं है। इसके बजाय keyword और params से पाठ बनाएँ और message logs के लिए रखें।

मेरा कस्टम error message अनदेखा क्यों हो रहा है?

पहले जाँचें कि allErrors: true चालू है; ajv-errors केवल उसी flag के साथ सभी संदेश बदल सकता है। फिर पुष्टि करें कि errorMessage विफल keyword के समान स्तर पर है — एक स्तर इधर-उधर और इसे पहचाना नहीं जाएगा।

प्रोडक्शन में कौन-से फ़ील्ड लौटाना सुरक्षित है?

फ़ील्ड path, स्थिर error code, और उपयोगकर्ता-मुखी पाठ। schemaPath, params और message server logs में रखें: इससे debugging समर्थित होती है और आंतरिक enums, regexes या फ़ाइल layout उजागर नहीं होते।