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.

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é:
instancePathje JSON Pointer podle RFC 6901. Prázdný řetězec znamená kořen;/items/0/priceznamená pole price prvního prvku pole.schemaPathzačíná#a ukazuje na klíčové slovo uvnitř dokumentu schématu. Napříč$refukazuje 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~1a~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ží:
- 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.
- Množná čísla a míry předejte knihovně i18n. Nikdy netvrďte „characters“ v šabloně;
1 charactera3 charactersse liší. - Překládejte i popisky polí.
params.missingPropertyjeemail, 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ů.