JSON Toolkit

JSON do TypeScriptu: rozhraní, typové strážce a limity generování

Shrnutí: Vygenerovat rozhraní ze vzorku JSON je jen polovina práce: volitelná pole, null, sjednocení, data a velká celá čísla potřebují lidský zásah a typy neprovádějí žádnou validaci za běhu.

Středně pokročilý~9 min čteníPublikováno 2026-10-31Aktualizováno 2026-11-04
Titulní obrázek průvodce JSON do rozhraní TypeScript a typovými strážci

Stručně: rozhraní vygenerujte, abyste ušetřili psaní, poté ručně zkontrolujte volitelnost a null, data a velká celá čísla ponechte jako řetězce a na hranici přidejte validaci za běhu; pokud vygenerované typy považujete za smlouvu, nemáte žádnou validaci.

Generátor vidí jen jeden vzorek, který do něj vložíte. Zda pole mohou chybět, zda hodnoty mohou být null a jaký formát data používají, jsou všechno hádanky – a špatné hádanky musíte opravit vy.

1. Co generátor vlastně dělá

Vložte vzorek JSON do JSON do TypeScriptu a vrácené rozhraní je shrnutím tvaru: spočítá klíče a typy hodnot, které vidělo, a poté zvolí nejužší dostupnou reprezentaci.

{
  "id": 1,
  "email": "ada@example.com",
  "profile": { "nickname": "Ada", "age": 36 },
  "tags": ["admin", "beta"],
  "createdAt": "2026-10-31T08:00:00Z"
}

Výstup vypadá zhruba takto:

interface User {
  id: number;
  email: string;
  profile: { nickname: string; age: number };
  tags: string[];
  createdAt: string;
}

Zkompiluje se to, ale popisuje ideální hodnotu, nikoli skutečnou smlouvu endpointu. Generátor neví, zda email může chybět, zda tags může být prázdné, nebo zda createdAt vždy nese časové pásmo. Neví dokonce ani, že id může v jiných záznamech být řetězec.

Správné očekávání tedy je: generátor ušetří psaní, význam dodá člověk.

2. Volitelná pole a null: ? není | null

Toto je část vygenereného výstupu, která se kopíruje nejčastěji, a část, která se nejčastěji láme.

Forma v TypeScriptu Význam Odpovídající JSON
email: string Vždy přítomné, vždy řetězec {"email":"a@b.c"}
email?: string Klíč může chybět; když je přítomen, nemůže být null {} nebo {"email":"a@b.c"}
email: string | null Klíč je vždy přítomen, hodnota může být null {"email":null}
email?: string | null Klíč může chybět a hodnota může být null Všechny tři případy

Generátor viděl jeden vzorek, takže obvykle vypíše první formu. Pokud API skutečně může vrátit null, typ lže: všechno se zkompiluje a poté email.toLowerCase() za běhu vyhodí chybu.

Opravit to lze dvěma způsoby:

  1. Sesbírejte několik skutečných vzorků pokrývajících „klíč chybí“ i „hodnota je null“ a nechte generátor odvozovat znovu.
  2. Po vygenerování ručně změňte pole na ?: T | null a v kódu skutečně ošetřete obě větve.

Neberte zkratku a neoznačujte vše jako ?: string. Volitelné znamená „klíč nemusí existovat“, nikoli „hodnota může být prázdná“. undefined a null se také chovají v JSON.stringify odlišně: první se vynechá, druhé se ponechá jako null.

3. Nerovnoměrné prvky pole a sjednocené typy

Pole jsou struktura, kterou nejsnáze pokazíte. Pokud se tags vyskytuje ve třech záznamech jako ["admin"], [] a ["beta", 3], generátor může vypsat string[], (string | number)[] nebo unknown[] podle toho, které vzorky viděl.

Člověk musí rozhodnout:

  • Typy prvků jsou skutečně smíšené: napište (string | number)[] a na místě použití zužte.
  • Špinavý je jen jeden záznam: opravte data. Neskrývejte to pomocí any[].
  • Prázdná pole: generátor nemůže odvodit typ prvku a vrátí se k never[] nebo any[], takže ho musíte doplnit.
  • N-tice pevné délky: použijte [string, string] jen tehdy, když obchod skutečně zaručuje délku; jinak se přidání prvku později stane chybou kompilace.

Pole objektů si také zaslouží pozornost, když jsou klíče nerovnoměrné:

interface OrderItem {
  sku: string;
  quantity: number;
  discount?: number; // některé záznamy toto pole nemají
}

Volitelnost dejte na typ prvku, nikoli na pole. items?: OrderItem[] znamená, že pole samotné může chybět, což je zcela jiné tvrzení.

4. Data, velká celá čísla, výčty: typy, které JSON nemá

JSON má jen řetězce a čísla; obchodní význam žije v konvencích. Generátor odvozuje z literálů, takže ho ručně opravte:

Literál JSON Obvyklý vygenerovaný výstup Co by to mělo být
"2026-10-31T08:00:00Z" string Ponechte string v přenosové vrstvě a převeďte na místě použití; nikdy nedeklarujte přímo Date
1717075200000 number Pro časové razítko v milisekundách použijte number a zdokumentujte jednotku
"1234567890123456789" string Ponechte string, aby number nemohl ztratit přesnost
1 a "1" smíšené number | string Normalizujte na jednu formu; netlačte sjednocení do obchodního kódu
"admin" string Literálové sjednocení 'admin' | 'editor', nebo sdílená konstanta výčtu
true a "true" smíšené boolean | string Opravte zdroj dat, místo abyste generovali tento typ

Nejdůležitější pravidlo: data v JSON jsou vždy řetězce. Zápis createdAt: Date je častý sebeklam – JSON.parse nikdy nevytvoří Date, takže hodnota za běhu je stále řetězec a .getTime() vyhodí chybu. Ponechte přenosový typ jako string, na hranici explicitně zavolejte new Date(value) a zkontrolujte Number.isNaN.

Velká celá čísla fungují stejně. Jakmile id přesáhne 2^53, JSON.parse už přesnost ztratil a generování number jen upevní chybu v typu. Backend vrací řetězec, frontend ho ponechá jako řetězec; to je jediná spolehlivá volba.

5. Vygenerované typy neprovádějí validaci za běhu

Typy TypeScriptu se při kompilaci vymažou. Ať je rozhraní sebepřesnější, nemůže zabránit odpovědi {"id": "x"} – omezuje váš vlastní kód, nikoli bajty přicházející po síti.

/**
 * Zkontroluje, zda je hodnota za běhu objekt uživatele.
 * @param value Hodnota za běhu k validaci
 * @returns Zda splňuje minimální tvar User
 */
function isUser(value: unknown): value is User {
  if (typeof value !== 'object' || value === null) {
    return false;
  }
  const candidate = value as Record<string, unknown>;
  return typeof candidate.id === 'number' && typeof candidate.email === 'string';
}

Typový strážce pokrývá jen pole, která zapíšete do kontroly. S přibývajícími poli se ručně psaní strážci stávají druhou definici k údržbě, která se vzdaluje od rozhraní.

Praktický kompromis vypadá takto:

Přístup Validace za běhu Zdroj typů Hodí se pro
Jen rozhraní Žádná Ručně psané nebo vygenerované Důvěryhodná interní data, omezení při kompilaci
Rozhraní plus ruční strážce Ano Dvě definice, které se rozcházejí Málo polí, jasná hranice
zod / valibot Ano Typy odvozené ze schématu Externí API, formuláře, konfigurace
JSON Schema plus validátor Ano Typy generované ze schématu Kontrakty napříč jazyky, dokumentace

Pokud data přicházejí zvenčí, udělejte schéma jediným zdrojem pravdy a odvoďte typy pomocí z.infer nebo json-schema-to-ts. Pak jsou „validace prošla“ a „typ je správný“ stejná skutečnost a neexistuje druhá definice, která by se rozcházela. Úplnému obousměrnému přístupu se věnuje Synchronizace schématu a typů TypeScript.

6. Interface nebo type: nehádejte se o vygenerený výstup

Generátory obvykle vypisují interface, ale oba konstrukty jsou ve většině situací vzájemně zastupitelné. Skutečně záleží na třech schopnostech:

  • Sloučení deklarací: interface lze deklarovat vícekrát a automaticky se sloučí; type ne. Hodí se při záplatování typů třetích stran, ale obchodní modely by se tomu měly vyhýbat – sloučení nechá „nadbytečná pole“ proklouznout kompilátorem bez povšimnutí.
  • Sjednocení a mapované typy: type umí přímo vyjádřit A | B, Partial<T> a typy šablonových literálů; interface ne.
  • Rekurze: oba ji podporují, ale složité rekurzivní tvary se čtou přirozeněji jako type.

Užitečnější týmové pravidlo: pro tvary objektů používejte interface, pro sjednocení, pomocné typy a typy funkcí type. Vygenerený výstup nechte ve výchozím stavu a čas při revizi věnujte volitelnosti a validaci za běhu.

7. Nástrojový řetězec: tři způsoby generování

Přístup Vstup Přednost Omezení
JSON do TypeScriptu na tomto webu Vzorek JSON Funguje okamžitě, zpracování lokálně Jen podle aktuálního vzorku; vyžaduje ruční zúžení
quicktype JSON / JSON Schema Vícejazyčný výstup, integrace do CLI Konzervativní odvozování, sjednocení mohou být příliš široká
json-schema-to-typescript JSON Schema Řízené kontraktem a reprodukovatelné Vyžaduje nejprve důvěryhodné schéma

Typické volání quicktype:

npx quicktype -s json -o user.ts user.json --just-types

--just-types vypíše pouze typy a žádný kód pro převod za běhu, což vyhovuje projektům, které už mají parsovací vrstvu. Pokud je schéma ručně psané nebo ho produkuje backend, json-schema-to-typescript se hodí víc, protože respektuje required, enum a additionalProperties – omezení, která odvozování ze vzorku prostě nevidí.

Generování schématu má vlastní úskalí, například označení všech polí jako povinných nebo tiché převádění null na volitelná pole; viz častá úskalí generátoru JSON Schema.

8. Kontrolní seznam před commitem vygenerovaných typů

Než vygenerený výstup committed, projděte těchto pět bodů:

  1. Normalizujte názvy polí: nenechte userId a user_id koexistovat; zmapujte je jednou v převodní vrstvě.
  2. Zkontrolujte volitelnost: porovnejte se skutečnými odpověďmi a najděte pole, která mohou chybět nebo být null.
  3. Ponechte data a id jako řetězce: zdokumentujte jednotku a formát v typu a na místě použití parsujte explicitně.
  4. Snižte any a unknown: kdekoli generátor vypsal any, buď přidejte typ, nebo ponechte unknown a přidejte strážce.
  5. Přidejte validaci za běhu: externí data validujte alespoň jednou; nečekejte, že rozhraní špatná data zachytí.

Pokud projekt používá několik JSON knihoven (JSON.parse, superjson, lossless-json), jejich chování u velkých celých čísel a dat se liší, takže to potvrďte už při výběru. Viz Výběr knihovny pro parsování JSON.

Proč generátor označí každé volitelné pole jako povinné?

Viděl jediný vzorek, takže každý klíč, který se objevil, považuje za vždy přítomný. Poskytněte více vzorků pokrývajících různé případy, nebo generujte z JSON Schema – pole required je tam autoritativním zdrojem.

Proč deklarace Date v rozhraní za běhu selhává?

JSON.parse produkuje jen řetězce, čísla, booleany, null, objekty a pole; nikdy nevytvoří Date. Deklarace Date je slib při kompilaci, zatímco hodnota za běhu zůstává řetězec. Ponechte string v přenosové vrstvě a převeďte na hranici.

Lze vygenerované typy použít jako validaci?

Ne. Typy TypeScriptu se po kompilaci vymažou a na data za běhu nekladou žádné požadavky. Externí data validujte pomocí zod, valibot, ajv nebo ručně psaného typového strážce.

Má být id s velkým celým číslem typováno jako number, nebo string?

Pokud může přesáhnout 2^53, musí to být string. JSON.parse během parsování ztrácí přesnost a typování jako number jen zapíše chybu do typu, kde už ji nelze opravit.