Vai al contenuto
jsonbeautifiers
Italiano

JSON in TypeScript

Interfacce unite su tutti gli elementi dell’array, così i campi opzionali lo sono davvero.

JSON
TypeScript

Nulla di ciò che incolli lascia il tuo browser. L’allowlist connect-src ne fa una garanzia del browser anziché una promessa. Verificalo tu stesso

Trasforma un payload JSON in interfacce TypeScript. Gli oggetti annidati diventano tipi con un nome proprio, gli array vengono uniti su tutti gli elementi, e le forme strutturalmente identiche vengono emesse una volta sola e riutilizzate.

Dedurre da un campione è tirare a indovinare. Il senso di questa pagina è che le ipotesi restano visibili.

I campi opzionali, dove quasi tutti i generatori sbagliano

Un generatore che legge l’elemento zero di un array e si ferma produce un’interfaccia in cui ogni campo è obbligatorio. Il primo record che omette un campo opzionale non supera il controllo dei tipi, e finisci per modificare a mano i tipi generati, il che vanifica tutto.

Questo unisce ogni elemento. Una chiave presente in tutti è obbligatoria; una chiave presente solo in alcuni viene marcata opzionale con un punto interrogativo. È informazione reale, estratta dal tuo campione, che un generatore a elemento singolo butta via.

Le altre decisioni

null diventa un’unione
Un campo visto sia come stringa sia come null è string | null, non any e non string.
Un array vuoto è unknown[]
Non any[]. Un array vuoto non porta informazione di tipo, e any[] spegnerebbe in silenzio i controlli per tutto ciò che lo tocca.
Le forme ripetute vengono deduplicate
Un elenco di cinquecento record produce un’interfaccia, non cinquecento.
I nomi degli elementi degli array vengono messi al singolare
Un array categories produce un’interfaccia Category.
Gli identificatori non validi vengono virgolettati
Una chiave has-dash, 2fast o class diventa un nome di proprietà tra virgolette.
Gli interi non sicuri vengono segnalati
Un campo tipizzato number quando il campione conteneva un intero sopra 2^53-1 è una bugia, perché JavaScript non riesce a rappresentarlo. La nota lo dice; la correzione giusta è a monte, inviandolo come stringa.

Che cosa un tipo generato non ti dà

Un’interfaccia TypeScript viene cancellata a runtime. Dice al compilatore che cosa ti aspetti e non fa assolutamente nulla quando l’API manda altro. Per quello serve un validatore a runtime, e il flusso onesto per un’API esterna è uno schema che valida e deduce il tipo dalla stessa definizione.

Zod, Valibot e ArkType fanno tutti questo. Genera qui un’interfaccia per capire un payload, poi scrivi lo schema a runtime per il confine che non controlli.

How to do this in code

La stessa idea nel codice, e che cosa usare su un confine di cui non ti fidi.

sh quicktype

quicktype supporta molti linguaggi di destinazione. Passare più campioni è l’opzione che conta davvero.

npx quicktype --lang ts --just-types --src-lang json payload.json

# Several samples, which is what makes optionality accurate
npx quicktype --lang ts --just-types samples/*.json
ts Zod

È il pattern da usare al confine di un’API. L’interfaccia lo dice al compilatore; lo schema dice la verità.

import { z } from 'zod';

const User = z.object({
  id: z.number(),
  name: z.string(),
  email: z.string().email(),
  verifiedAt: z.string().nullable(),
  roles: z.array(z.string()),
});

// One definition, both a runtime check and a static type
type User = z.infer<typeof User>;

const result = User.safeParse(await res.json());
if (!result.success) console.error(result.error.issues);
ts Solo tipi, da un literal
// If the data is a constant you control, TypeScript can infer
// the type without a generator at all.
const config = {
  retries: 3,
  endpoints: ['a', 'b'],
} as const;

type Config = typeof config;

Domande frequenti

Perché un campo risulta opzionale se la mia API lo manda sempre?
Perché almeno un record del campione non ce l’aveva. O è davvero un campo opzionale, o il campione è incompleto. Incolla un array con più record e la risposta migliora.
Meglio interface o type?
Per le forme di oggetti cambia pochissimo. Le interfacce supportano il declaration merging e producono messaggi di errore un po’ più gradevoli; gli alias di tipo possono esprimere unioni e mapped type. Qui sopra ci sono entrambi.
Questi tipi validano qualcosa a runtime?
No. I tipi TypeScript vengono cancellati in compilazione. Descrivono ciò che credi; non lo verificano. Per dati che attraversano un confine di rete, usa un validatore a runtime.