Da JSON a TypeScript

Genera interfacce TypeScript dal JSON: elementi di array uniti, chiavi opzionali, union di tipi misti, null distinto — tutto nel browser.

Input
TypeScript
export interface Root {
  id: number
  name: string
  active: boolean
  address: Address
  roles: string[]
  posts: Post[]
  tags: unknown[]
}

export interface Address {
  city: string
  zip: null
}

export interface Post {
  id: number
  title: string
  views: number | string
  pinned?: boolean
}

Interfacce: 3

Tipi dedotti dai dati, non indovinati

Un documento JSON non porta i propri tipi: porta valori, e i tipi vanno riletti a partire da essi. È facile per un singolo oggetto e sorprendentemente sottile per una raccolta: la forma che vuoi non è quella di un singolo record, ma quella che ogni record deve soddisfare. Questo strumento deduce interfacce TypeScript da un campione JSON esaminando tutto ciò che il campione contiene, così i tipi prodotti descrivono l'insieme dei tuoi dati e non la prima riga che si è trovata in cima.

La conversione va in una sola direzione. Riconvertire i tipi in JSON significherebbe inventare valori, e qui l'obiettivo è l'opposto: il documento incollato è la fonte di verità, e ogni dichiarazione ne deriva. Incolla una risposta di un'API, un file di configurazione o una riga di log, e leggi l'interfaccia che altrimenti avresti scritto a mano.

Come vengono uniti gli array

Le decisioni interessanti avvengono tutte negli array. Un convertitore ingenuo guarda il primo elemento e si ferma, facendo sembrare obbligatorio ogni campo che vede per caso e mancando ogni campo che non vede. Questo strumento invece unisce tutti gli elementi in un unico tipo, e ne derivano tre cose:

  • Una chiave presente in alcuni elementi ma assente in altri diventa opzionale, scritta con un punto interrogativo. Se metà dei tuoi record ha "middleName" e metà no, il campo è "middleName?", esattamente ciò che un consumatore deve gestire.
  • Una chiave il cui valore differisce per tipo tra gli elementi diventa una union. Un campo che è un numero in un record e una stringa in un altro è tipato "number | string", non per ordine, ma perché è ciò che i dati contengono davvero e ciò che il tuo codice deve accettare.
  • Un oggetto annidato dentro gli elementi viene unito allo stesso modo, ricorsivamente, ed estratto in una propria interfaccia. Dieci elementi di un array che portano ciascuno un "address" producono una sola interfaccia Address che descrive tutti e dieci.

Quando il documento è esso stesso un array al livello superiore, la radice diventa un alias a un'interfaccia di elemento — per esempio "type Root = RootItem[]" — con il tipo dell'elemento unito scritto sotto.

Null, opzionale, e perché sono diversi

È allettante trattare un null come una chiave assente, ed è sbagliato. In TypeScript "name?: string" significa che la proprietà può mancare; "name: string | null" significa che è sempre presente ma può contenere null. Sono contratti diversi, e un consumatore li verifica in modo diverso — "in" contro un confronto di valore. Questo strumento li tiene distinti: un null esplicito nei dati diventa un membro di union "| null", posto per ultimo così che "string | null" si legga come ti aspetti, mentre una chiave semplicemente assente da alcuni record diventa opzionale. Un campo che è entrambe le cose — null in un record, assente in un altro — esce come entrambe, "field?: T | null", perché entrambi i fatti sono veri dei tuoi dati.

Gli oggetti annidati diventano interfacce con nome

Invece di incorporare una forma annidata nel suo genitore, ogni oggetto viene estratto in una propria interfaccia il cui nome deriva dalla chiave sotto cui si trova. Un oggetto "user" diventa un'interfaccia User; un "address" al suo interno diventa un'interfaccia Address a cui User fa riferimento. I tipi incorporati in profondità sono difficili da leggere e impossibili da riusare, e le interfacce con nome sono ciò che avresti scritto tu stesso. Gli elementi di un array vengono messi al singolare quando è possibile — "users" dà un User, "categories" un Category — e una chiave che non si pluralizza riceve il suffisso Item così che l'elemento abbia un nome proprio.

Se due oggetti diversi prendessero lo stesso nome — due "data" non correlati, per esempio — il secondo riceve un suffisso invece di essere unito, così che forme distinte restino distinte. L'oggetto radice viene emesso per primo e puoi rinominarlo; la scelta tra output "interface" e "type" è un interruttore, poiché alcune basi di codice preferiscono gli alias di tipo ovunque.

Il ripiego su unknown

Alcuni valori non portano alcuna informazione di tipo. Un array vuoto potrebbe contenere qualsiasi cosa; un oggetto vuoto non ha chiavi da descrivere. Invece di ricorrere ad "any" — che disattiva il controllo dei tipi per tutto ciò che segue — lo strumento ripiega su "unknown": un array vuoto diventa "unknown[]", un oggetto vuoto diventa "Record<string, unknown>". La differenza conta. "any" lascia passare i bug in silenzio; "unknown" costringe il consumatore a restringere il valore prima di usarlo, così il tipo dedotto resta onesto su ciò che il campione ha detto e non ha detto.

Sono i tipi che stringeresti a mano non appena saprai cosa deve contenere la raccolta vuota — ma finché i dati non lo dicono, "unknown" è la risposta onesta, ed è quella che mantiene sicuri gli altri tuoi tipi.

Su cosa gira, e dove

Tutto avviene nel tuo browser. Il JSON viene analizzato e i tipi dedotti sul tuo dispositivo; nulla di ciò che incolli viene caricato, memorizzato o registrato. Questo rende lo strumento sicuro da usare su una vera risposta di un'API o un file di configurazione con dei segreti dentro: il campione non lascia mai la pagina. L'output è normale TypeScript che puoi incollare direttamente in un file "d.ts" o in un modulo, aggiustare i pochi campi "unknown" che i dati non hanno potuto descrivere, e usare.

Domande frequenti

Il mio JSON viene inviato a un server?
No. Il documento viene analizzato e i tipi dedotti interamente nel tuo browser, e nulla di ciò che incolli viene caricato o registrato. È sicuro da usare su una vera risposta di un'API o un file di configurazione.
Perché un campo è opzionale se è presente nel mio campione?
Perché è assente da almeno un elemento di un array che lo strumento ha unito. L'inferenza legge ogni elemento, non solo il primo, quindi una chiave che alcuni record omettono diventa opzionale — è il tipo che i tuoi dati ammettono davvero, anche se il record che hai guardato per caso la includeva.
Perché un campo è diventato una union come string | number?
Perché il valore aveva tipi diversi in diversi elementi dell'array — una stringa in un record e un numero in un altro. Il tipo unito deve accettare entrambi, quindi è scritto come una union. Se ti sorprende, di solito significa che i dati sono meno uniformi del previsto, cosa che vale la pena sapere.
Perché lo strumento usa unknown invece di any?
Per i valori che non può descrivere — un array vuoto, un oggetto vuoto — "unknown" mantiene il risultato sicuro sui tipi, costringendo un consumatore a restringere il valore prima di usarlo, mentre "any" disattiverebbe il controllo dei tipi. Puoi stringere quei campi a mano non appena saprai cosa contiene la raccolta vuota.
Qual è la differenza tra l'output interface e type?
Nessuna nei tipi che descrivono — entrambi producono le stesse forme. "interface" è l'idioma comune per i tipi oggetto e può essere esteso e unito; gli alias "type" sono ciò che alcune basi di codice preferiscono usare ovunque. L'interruttore c'è perché l'output corrisponda allo stile del tuo progetto.
Può riconvertire il TypeScript in JSON?
No, e di proposito. La conversione è a senso unico: JSON in entrata, tipi in uscita. Andare nell'altra direzione significherebbe inventare valori mai stati nei tuoi dati, e tutto il senso è che ogni dichiarazione deriva da ciò che hai davvero incollato.
Come vengono nominati gli oggetti annidati?
Dalla chiave sotto cui si trovano: un oggetto "user" diventa User, un "address" al suo interno diventa Address. Gli elementi di un array vengono messi al singolare quando è possibile — "categories" dà Category — e una chiave che non si pluralizza riceve il suffisso Item. Due forme diverse che collidono su un nome ricevono un suffisso invece di essere unite, così restano distinte.