JSON Toolkit

Když chyby JSON Schema nedávají smysl: lokalizace a správa zpráv v Ajv

Shrnutí: Validátor vydává strojové chyby; uživatel chce vědět, které vstupní pole je špatné. Pokrývá pole errors v Ajv, instancePath versus schemaPath, seskupování a formulaci.

Pokročilý~10 min čteníPublikováno 2026-10-31Aktualizováno 2026-10-31
Diagram lokalizace validačních chyb Ajv a správy zpráv

Stručně: nejprve čtěte instancePath k lokalizaci selhávajícího místa v datech, poté schemaPath k lokalizaci pravidla, které se spustilo; u required a additionalProperties vytáhněte skutečný název klíče z params, než zvolíte zprávu.

Úkol validátoru je říci, které pravidlo selhalo. Přeložit to do „toto vstupní pole je špatné“ je váš úkol.

1. Co vám pole errors vlastně dává

Ajv při selhání validace nevyhazuje; výsledek nechává v validate.errors. Výchozí konfigurace drží jen první chybu a allErrors je to, co produkuje úplný seznam. Jediný chybový objekt má pět důležitých polí a jejich pochopení je předpokladem všeho dalšího:

Pole Význam Příklad
instancePath JSON Pointer na selhávající hodnotu /email
schemaPath Umístění selhávajícího klíčového slova ve schématu #/properties/email/format
keyword Název porušeného omezení format
params Strojově čitelné parametry toho omezení { "format": "email" }
message Anglická próza, která se mezi verzemi mění must match format "email"

Skutečná chyba vypadá takto:

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

Pozor, message je próza psaná pro lidi, nikoli stabilní rozhraní. Ajv ji mezi verzemi může přeformulovat a nemůže sloužit lokalizovanému produktu. Nikdy ji neparsujte regulárním výrazem a nikdy ji nevracejte klientovi doslovně.

2. instancePath a schemaPath odpovídají na různé otázky

instancePath říká, který úsek dat selhal; schemaPath říká, které pravidlo se spustilo. Jsou to dva souřadnicové systémy a nejsou symetrické:

  • instancePath je JSON Pointer podle RFC 6901. Prázdný řetězec znamená kořen; /items/0/price znamená pole price prvního prvku pole.
  • schemaPath začíná # a ukazuje na klíčové slovo uvnitř dokumentu schématu. Napříč $ref ukazuje dovnitř referencovaného schématu, takže potřebujete $id, abyste věděli, ze kterého souboru pochází.
  • Klíč obsahující / nebo ~ je escapován na ~1 a ~0. Zrušte to před zobrazením, jinak UI ukáže matoucí symboly.

Například instancePath: "/email" spolu s schemaPath: "#/properties/email/format" znamená, že pole email na kořenovém objektu neprošlo kontrolou formátu.

Nejčastější past je required a additionalProperties: jejich instancePath ukazuje na rodičovský objekt, nikoli na problematický klíč.

Klíčové slovo instancePath ukazuje na Kde žije skutečný klíč
required Rodičovský objekt (v kořeni prázdný řetězec) params.missingProperty
additionalProperties Rodičovský objekt params.additionalProperty
type Selhávající hodnotu samotnou —
enum Selhávající hodnotu samotnou params.allowedValues

Při mapování chyb na pole formuláře ošetřete tyto dva případy nejprve. Jinak se „email chybí“ stane „chyba kořenového objektu“ a uživatel nemá tušení, kam psát.

3. allErrors a verbose: úplnost versus cena

Výchozí allErrors: false nutí Ajv zastavit u prvního selhání. To je v pořádku pro nástroj příkazového řádku a katastrofální pro formulář: uživatel opraví jedno pole, odešle, uvidí další chybu a opakuje to třikrát, než formulář projde.

Nastavení Chování Přínos Cena
allErrors: false Jen první chyba Nejrychlejší, nejmenší paměť Opakované odesílání
allErrors: true Shromáždit každou chybu Všechny problémy v jednom průchodu Počet chyb exploduje u složitých schémat
verbose: true Připojit schema a data ke každé chybě Snadnější diagnostika na serveru Výrazně větší velikost; nikdy nevystavovat
messages: false Zahodit pole message Menší payloady Ztráta anglické ladicí stopy

Doporučené rozdělení: zapněte allErrors: true pro validaci na serveru a držte úplné pole v interních logech, zatímco veřejná odpověď nese jen whitelistovaná pole. verbose zapínejte jen při vyšetřování; vkládá odpovídající fragment schématu a uživatelská data do každé chyby, což stojí paměť a může unést citlivé hodnoty do logů.

Objem chyb je další běžný problém. Selhávající větve anyOf vydává několik podřízených chyb plus jednu souhrnnou a oneOf s mnoha větvemi je horší. Zobrazte pole tak, jak je, a uživatelé uvidí pět téměř duplicitních zpráv pro jedno pole. Seskupování musí proběhnout před vykreslením.

4. Seskupte chyby podle pole

Seskupování dělá tři věci: mění cestu zpět na název pole, opravuje offset u required a additionalProperties a slučuje více chyb na stejném poli do jedné nejužitečnější zprávy.

/** Jedna validační chyba připravená k zobrazení */
interface FieldError {
  field: string;
  keyword: string;
  params: Record<string, unknown>;
}

/**
 * Seskupí chyby Ajv podle pole.
 * @param errors Pole chyb produkované Ajv
 * @returns Mapa klíčovaná cestou pole, každá hodnota vypisuje jeho chyby
 */
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;
}

Slučujte s prioritním pořadím: nejprve required (chybí), poté type, poté enum/const, poté format/pattern, poté délka a rozsah. Zobrazte jen zprávu s nejvyšší prioritou pro každé pole a zbytek držte v logech. Důvod je přímočarý: když je typ špatný, zpráva o délce je šum a oprava typu je jediný rozumný další krok.

Souhrnné chyby pro oneOf a anyOf obvykle nenesou žádnou užitečnou informaci. Přeskočte je při vykreslování a držte jen chyby větví, které ukazují na konkrétní pole.

5. Vlastní zprávy: cesta pluginu a cesta šablon

Ajv nedodává lokalizované formulace, takže existují dvě praktické možnosti.

Možnost jedna: plugin ajv-errors. Přidává klíčové slovo errorMessage, takže zprávy žijí vedle omezení:

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

Výhodou je, že formulace cestuje s polem a schéma slouží zároveň jako dokumentace. Nevýhodou je, že text je zmrazený v jednom jazyce, takže lokalizace znamená údržbu několika schémat. Plugin také nahradí každou zprávu jen tehdy, když je zapnuto allErrors: true; zapomeňte ten příznak a dostanete směs vlastních a výchozích anglických zpráv.

Možnost dvě: tabulka klíčové slovo → šablona. Validátor dodává jen keyword a params; formulaci produkuje aplikace:

Klíčové slovo Šablona Dostupné parametry
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 (neopakujte ho)

Možnost dvě se hodí produktům, které potřebují více jazyků a konzistentní hlas. Odděluje validaci od prezentace, drží překlad mimo schéma a nechává frontend znovu použít stejné šablony.

6. Lokalizace: zprávy nepatří validátoru

Přesun formulací do jazykových balíčků je výchozí bod správy zpráv. Jazykový balíček ukládá šablony a klíče, nikdy názvy polí; popisky polí pocházejí z konfigurace formuláře nebo z kontraktu API a obojí se kombinuje při vykreslení.

/** Šablony validačních zpráv klíčované podle klíčových slov Ajv */
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',
};

/**
 * Sestaví jednu validační zprávu pro uživatele.
 * @param keyword Název selhavšího omezení
 * @param params Parametry omezení
 * @returns Lokalizovaný text zprávy
 */
function buildMessage(keyword: string, params: Record<string, any>): string {
  const template = VALIDATION_TEMPLATES[keyword];
  return template ? template(params) : 'The value is invalid';
}

Tři pravidla zde záleží:

  1. Neskládejte věty. „Email format is invalid“ a jeho český ekvivalent nemají stejný slovosled a skládání řetězců nakonec v jednom z nich vyprodukuje nesmysl.
  2. Množná čísla a míry předejte knihovně i18n. Nikdy netvrďte „characters“ v šabloně; 1 character a 3 characters se liší.
  3. Překládejte i popisky polí. params.missingProperty je email, zatímco UI má říct „E-mailová adresa“. To mapování držte v konfiguraci formuláře, nikoli v jazykovém balíčku.

7. Nevracejte klientům interní strukturu

Vracet schemaPath, params a message společně je pohodlné během vývoje a v produkci únik informací. allowedValues prozrazuje interní výčty, pattern prozrazuje validační regulární výrazy a schemaPath prozrazuje rozvržení souborů a pojmenování polí – v některých případech předává útočníkům mapu vašich slabých míst.

Veřejná odpověď drží přesně tři věci: cestu k poli (nebo název pole formuláře), stabilní kód chyby a text pro uživatele. Používejte krátké stabilní kódy místo surového keyword, abyste si nechali prostor změnit validátor později.

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

Serverové logy mají držet opak: úplné pole chyb, id požadavku a verzi schématu, aby vyšetřování umělo odpovědět „které pravidlo to zablokovalo“. Vyhněte se vysypávání celých těl požadavků do logů; držte místo toho selhávající cesty a klíčová slova.

8. Prezentace na frontendu: od zvýraznění pole ke značkám v editoru

Chyby nakonec přistanou v UI a prezentace závisí na kontextu:

Kontext Prezentace Klíčová akce
Formulář Červený text pod polem, fokus na první chybu Mapujte cesty na názvy formuláře; držte indexy pole
Hromadný import tabulky Číslo řádku, název sloupce, shrnutí, exportovatelné Převeďte /items/3/price na řádek 4, sloupec price
Editor JSON Inline značky a panel problémů Použijte parser k mapování JSON Pointeru na řádek a sloupec
Nástroj pro ladění API Strukturovaný seznam chyb držící schemaPath Pro vývojáře, takže více detailů je přijatelné

Tři poznámky k implementaci:

  • Zobrazte jen první chybu pro každé pole a zbytek sbalte do „2 more issues“, aby se formulář neutopil v červené.
  • Držte indexy pole pro cílení, ale v popiscích je vynechejte. „Řádek 4, price“ se čte lépe než /items/3/price.
  • Scénáře editoru potřebují mapování pozic. Prosté hledání řetězce je nespolehlivé, když se název pole opakuje; parser by měl poskytnout offsety pro každý uzel a ty poté převedete na řádek a sloupec.

Pokud JSON pochází od uživatele píšícího, nikoli z formuláře, nejprve potvrďte, že dokument je syntakticky platný. Syntaktické chyby vznikají při parsování a nikdy se nedostanou k validaci schématem. Parsování a formátování JSON hlásí přesný řádek a sloupec, což je mnohem rychlejší než honění chyb schématu. Jak vznikají příliš přísná schémata, když se odvozují ze vzorků, najdete v Proč automaticky vygenerovaná JSON Schema stále odmítá skutečná data; konfiguraci validátoru a systému klíčových slov v JSON Schema: od základů k produkci.

9. Často kladené otázky

Proč dostávám jen jednu chybu?

allErrors je ve výchozím nastavení false, takže Ajv se vrátí u prvního selhání. Pro validaci formuláře ho explicitně zapněte; jinak uživatelé musí opakovaně odesílat, aby objevili všechny problémy.

Proč je cesta u chyby required prázdná?

instancePath u required ukazuje na rodičovský objekt, což je v kořeni prázdný řetězec; název chybějícího klíče sedí v params.missingProperty. Spojte obojí, abyste dostali úplnou cestu. Zobrazení samotného instancePath dává neužitečnou „chybu kořenového objektu“.

Můžu uživatelům ukázat message přímo?

Nedoporučuje se. Je to anglická próza, jejíž formulace se mezi verzemi mění, a nemá schopnost lokalizace. Generujte text z keyword a params a message si nechte pro logy.

Proč je moje vlastní chybová zpráva ignorována?

Nejprve zkontrolujte, že je zapnuto allErrors: true; ajv-errors umí nahradit všechny zprávy jen s tímto příznakem. Poté potvrďte, že errorMessage sedí na stejné úrovni jako selhávající klíčové slovo – o úroveň vedle a nebude rozpoznáno.

Která pole je bezpečné vracet v produkci?

Cestu k poli, stabilní kód chyby a text pro uživatele. schemaPath, params a message držte v serverových logech: podporuje ladění, aniž by vystavovalo interní výčty, regulární výrazy nebo rozvržení souborů.