TypeScript
TypeScript
Vue
Vue
Premium
Premium

Úvod do knihovny Zod

Pavel  JaklPavel Jakl,

Při vývoji aplikace se často setkáváme se situací, kdy potřebujeme zaručit, že data, se kterýma pracujeme, mají požadovaný formát či strukturu. Pokud na projektu využíváme TypeScript, tak si můžeme pomoci, jelikož TS dokáže ověřit, zda hodnota je taková, jakou očekáváme, ale co kdybychom chtěli jít ještě více do detailu a ověřit, zda daná hodnota je například moc nízká nebo řetězec obsahuje příslušný počet znaků? A co třeba využití API třetích stran? Zde si můžeme projít dokumentaci, abychom zjistili, co nám API vrátí, ale TS nám už neřekne, jak odpovědi přiřadit typ nebo jak iterovat skrz poskytnutá data. 

Jak na tuto situaci reagovat v komponentách a jak zaručit to, že do komponent přijdou jen ty data, které očekává? 

Na tyto otázky si odpovíme v tomto článku, který se bude věnovat TS knihovně Zod. 

Zod (dokumentace) je knihovna pro deklaraci a validaci schémat v jazyce TypeScript. Schéma zde označuje široký pojem jakéhokoli typu, od jednoduchého řetězce až po složitý vnořený objekt. Je navržen tak, aby byl co nejpřívětivější pro vývojáře. Cílem je eliminovat duplicitní deklarace typů. Pomocí Zodu jednou deklarujete validátor a Zod automaticky odvodí statický typ jazyka TypeScript. Jednodušší typy lze snadno skládat do složitých datových struktur.

Jeho výhodou je, že umožňuje obejít nejistotu kontrolou typů díky kontrole na úrovni běhu i na úrovni typů. 

Tímto bych ukončil stručný úvod a přejdeme k praktickému využití zodu :) 

Instalace, vytvoření schématu a jednoduchá validace

Instalace

Zod má určité požadavky pro instalaci knihovny: 

  • TypeScript 4.5+
  • podle best practice pro TS projekty povolit strict mode v tsconfig.json 

Instalaci Zodu provedeme jednoduše pomocí správce balíčků npm, ale jsou i jiné možnosti (yarn, pnpm, bun …)

pnpm install zod

Typy

Zod poskytuje širokou škálu typů, které můžeme využít. Od primitivních typů jako string, number, int až po pole či objekty nebo vynucení primitivních typů (coercion) či literály. Dále pak poskytuje možnosti omezení primitivních typů, jako kontrola minimální a maximální hodnoty, počet znaků, url a mnoho dalšího. Všechny informace jsou přehledně sepsány v dokumentaci.

Vytvoření základního schématu a validace

Pro vytvoření schématu stačí importovat Zod a následně vytvořit validační schéma. Následující příklad nám říká, že schéma se jmenuje age a má to být číslo. Když chceme validovat nějakou hodnotu podle tohoto schématu, jednoduše napíšeme jeho název, klíčové slovo parse a do závorek hodnotu. Pokud validace proběhne v pořádku, vrátí nám hodnotu, kterou jsme validovali. Pokud ne, vyhodí ZodError.

import {z} from "zod" 

const age = z.number()

age.parse(18) // validace proběhne v pořádku => 18

age.parse("a") // => ZodError 


Struktura erroru

ZodError: [
  {
    "code": "invalid_type",
    "expected": "number",
    "received": "string",
    "path": [],
    "message": "Expected number, received string"
  }
]

Parse vs safeParse

Zod nám dává na výběr ze dvou metod parsování:

  • parse
  • safeParse

Obě metody dělají to samé, tedy validují poskytnutou hodnotu podle daného schématu, ale liší se tím, že metoda parse po nalezení chyby bezprostředně vyvolá výjímku . Oproti tomu metoda safeParse projde celé schéma a na konci nám vrátí objekt, který obsahuje informace o tom, zda byla validace úspěšná a případně chyby ve formátu ZodError.

console.log(MySchema.safeParse(data))

// výsledek safeParse metody při nalezení chyby
{
error: ZodError: [{}]
success: false
}

Obě tyto metody mají své výhody i nevýhody, záleží tak na konkrétním scénáři a využití v kontextu řešeného problému.

Parse

Výhody

  • přímočarost - bezprostřední vyvolání chyby po jejím nalezení
  • tok aplikace - pro řešení chyb ihned po jejich výskytu
  • čistota kódu - občas užitečné, pokud nechceme na chyby hned reagovat a dále je zpracovávat

Nevýhody

  • vyvolání výjimek - občas může být nákladné z hlediska výkonu
  • try-catch blok - pro správné fungování a zachycení chyb
  • menší kontrola nad zpracováním chyb - chyba je vyvolána ihned a nemáme možnost ovlivnit, jak chybu zpracovat

safeParse

Výhody

  • bezpečnější zpracování chyb - bezpečnější zpracování chyb, vrací objekt s výsledkem validace
  • flexibilní zpracování chyb: Poskytuje větší kontrolu nad tím, jak jsou chyby zpracovány, což umožňuje jenější reakce na různé typy chyb.
  • vhodné pro uživatelská rozhraní: V situacích, kde je potřeba zobrazovat chyby uživatelům (např. ve webových formulářích), je safeParse ideální volbou.

Nevýhody: 

  • potřeba dalšího kódu pro kontrolu výsledků: Musíte explicitně kontrolovat výsledek volání safeParse a rozhodnout, jak dále postupovat.
  • méně přímočaré pro jednoduché scénáře: někdy může být více komplexní, než je třeba
  • méně strukturovaný kód: Pokud není správně implementováno, může způsobit, že kód bude mít více větví a bude méně čitelný.

S chybami můžeme pak dále pracovat. Můžeme je jednoduše vypsat do konzole, můžeme upravit jejich formát a předat je dál, například, poslat do externího nástroje, který sbírá logy z aplikace nebo s nimi v případě potřeby dál pracovat. 

Type inference

Jak název odpovídá, tento koncept se zabývá odvození typu z definovaného schématu. Docílíme toho jednoduše, definujeme si novou konstantu, které řekneme, že má mít typ odvozený z příslušného schématu následujícím způsobem infer<typeof schema>. V praxi bychom řekli, že chceme vytvořit schéma, pomocí kterého budeme data validovat. Po úspěšné validaci chceme procházet data pomocí loop cyklu. Aby byl TS spokojený, měli bychom funkci, která bude data procházet, dát i typ dat. Ze schématu si tedy odvodíme typ, který následně použijeme jako typ vstupního parametru funkce:

type MySchema = z.infer<typeof MySchema>; 

Transformace

Tímto tedy máme typ, který je odvozený podle schématu. Zod schéma ale interně sleduje dva typy a to vstup a výstup. Tyto dva typy se mohou lišit. Uvedeme si jednoduchý příklad: 

const stringToNumber = z.string().transform((val) => val.length);

Pokud si schéma rozebereme, máme tedy vstup a výstup:

  • Vstup (input): definovali jsme, že vstupní parametr musí být typu string
  • Výstup (output): transform((val) => val.lenght transformuje vstupní hodnotu, kde výsledkem bude délka vstupního řetězce

Takže když to shrneme, tak schéma očekává na vstupu řetězec, a výstupní typ bude číslo. 

const result = stringLengthSchema.parse("hello");
console.log(result); // Vypíše 5

Pokud chceme odvodit typ, jak jsme si již ukázali výše, je důležité si uvědomit, že při použití z.infer dostaneme výstupní typ schématu. Pokud bychom chtěli vstupní typ, musíme použít z.input<typeof …>.

//vstupní typ, 
type input = z.input<typef stringLengthSchema> //string

//výstupní typ, shodný s z.infer
type input = z.output<typef stringLengthSchema> //number

 

Premium obsahZískej premium obsah a osobní přístup

Článek je součástí premium systému, podívej se na výhody premium účtu. Vytvářme kvalitný vzdělávací obsah, vedeme lidi k jejich kariérním cilům a udržujeme je v dění novinek ve webovém světě. Díky premium systému podpoříš náš projekt získaš řadu výhod:

Premium

  • článek přístup k premium obsahu (články, návody, vzdělávací materiály...)
  • kariéra nastartujeme tvoji IT kariéru
  • mentor dostaneš osobního mentora
  • github zašli nám kód a my ti uděláme code-review (kóderské výzvy)
  • discord přístup do zamčeného kanálu na Discordu
  • práce pomůžeme s prací
  • dotazy budeme ve spojení - ptej se na co potřebuješ
Pro aktivování premium se nejprve přihlaste na svůj účet.