← Tutti gli articoli

Come generare interface TypeScript da JSON

Impara a convertire JSON in interface TypeScript — a mano, con strumenti online e nel codice. Copre oggetti annidati, campi opzionali, array, tipi nullable e come tenere i tipi allineati alla tua API.

Ogni sviluppatore TypeScript si è trovato in questa situazione: stai integrando una nuova API, hai una risposta di esempio e ti servono interface tipizzate prima di poter scrivere una sola riga di logica di business. Scrivere quelle interface a mano è noioso, ripetitivo e soggetto a errori — soprattutto quando la risposta è molto annidata o ha decine di campi. Questa guida copre tutti gli approcci pratici per convertire JSON in interface TypeScript: come dovrebbe essere l'output, come generarlo automaticamente e come gestire i casi complicati.

Perché le interface TypeScript contano per i dati JSON

Quando chiami un'API in TypeScript senza annotazioni di tipo, la risposta parsata è any. Significa niente autocompletamento, niente type checking e niente sicurezza a compile-time. Un refuso in un nome di proprietà diventa un bug a runtime invece di un errore di build.

// Senza tipi — tutto è any
const response = await fetch('/api/users/42');
const user = await response.json(); // user: any
console.log(user.naem); // refuso — nessun errore a compile-time

// Con una interface tipizzata
interface User {
  id: number;
  name: string;
  email: string;
}

const response = await fetch('/api/users/42');
const user: User = await response.json();
console.log(user.naem); // Error: Property 'naem' does not exist on type 'User'

L'interface aggiunge zero costo a runtime — esiste solo a compile-time. Ma cambia completamente l'esperienza di sviluppo.

Cosa produce una conversione JSON-verso-TypeScript

Ogni oggetto JSON diventa una interface TypeScript, ogni primitiva JSON viene mappata sul suo equivalente TypeScript, e gli array inferiscono il tipo degli elementi.

// JSON di input
{
  "id": 42,
  "name": "Alice Johnson",
  "email": "alice@example.com",
  "active": true,
  "score": 98.5,
  "address": {
    "street": "123 Main St",
    "city": "New York",
    "zip": "10001"
  },
  "tags": ["admin", "editor"],
  "orders": [
    { "id": 101, "total": 49.99, "status": "shipped" },
    { "id": 102, "total": 99.00, "status": "pending" }
  ]
}
// Output TypeScript
interface Root {
  id: number;
  name: string;
  email: string;
  active: boolean;
  score: number;
  address: Address;
  tags: string[];
  orders: Order[];
}

interface Address {
  street: string;
  city: string;
  zip: string;
}

interface Order {
  id: number;
  total: number;
  status: string;
}

Ogni oggetto annidato diventa un'interface con nome. Il nome viene dalla chiave padre — address diventa Address, gli elementi di orders diventano Order. Questo mantiene i tipi leggibili e componibili.

Le regole di mappatura dei tipi

Capire come i tipi JSON vengono mappati sui tipi TypeScript rende l'output prevedibile e ti permette di adattarlo con sicurezza.

Valore JSONTipo TypeScript
"hello"string
42, 3.14number
true, falseboolean
nullnull
{}interface con nome
['a', 'b']string[]
[1, 'two'](number | string)[]
[] (vuoto)unknown[]

Una sfumatura: TypeScript non ha un tipo intero. Sia 42 sia 3.14 diventano number. Se la distinzione conta per il tuo caso d'uso, puoi usare tipi brandizzati o aggiungere un commento.

Metodo 1: usare un convertitore JSON-verso-TypeScript online

Per attività spot — un nuovo endpoint API, un prototipo veloce — uno strumento online è la via più rapida. Il convertitore JSON-verso-TypeScript di fixjson esegue tutta la conversione nel tuo browser:

  1. Incolla il tuo JSON nel pannello di input. Lo strumento accetta JSON valido o JSON rotto con problemi comuni come virgole finali e apici singoli — ripara il JSON prima di convertirlo.
  2. Le interface TypeScript appaiono all'istante sulla destra — nessun pulsante da premere.
  3. Copia l'output e incollalo direttamente nel tuo file TypeScript.

Tutto gira in locale. Nessun dato viene inviato a server — importante quando il JSON con cui lavori contiene chiavi API, dati utente o configurazioni interne.

Metodo 2: scrivere la conversione in TypeScript

Quando devi generare i tipi a livello programmatico — come parte di uno script di build o di un generatore di codice — puoi scrivere la logica tu stesso. Ecco un'implementazione minima ma completa:

function jsonToTypeScript(value: unknown, rootName = 'Root'): string {
  const interfaces: string[] = [];
  const seen = new Set<string>();

  function cap(s: string) {
    return s ? s.charAt(0).toUpperCase() + s.slice(1) : 'Unknown';
  }

  function singular(name: string) {
    if (/ies$/i.test(name)) return name.slice(0, -3) + 'y';
    if (/[^s]s$/i.test(name)) return name.slice(0, -1);
    return name + 'Item';
  }

  function getType(val: unknown, name: string): string {
    if (val === null) return 'null';
    if (typeof val === 'boolean') return 'boolean';
    if (typeof val === 'number') return 'number';
    if (typeof val === 'string') return 'string';
    if (Array.isArray(val)) {
      if (val.length === 0) return 'unknown[]';
      const objs = val.filter(
        (v): v is Record<string, unknown> =>
          v !== null && typeof v === 'object' && !Array.isArray(v),
      );
      if (objs.length > 0) {
        const itemName = cap(singular(name));
        buildInterface(itemName, objs);
        return `${itemName}[]`;
      }
      const types = [...new Set(val.map((v) => getType(v, name)))];
      return types.length === 1 ? `${types[0]}[]` : `(${types.join(' | ')})[]`;
    }
    if (typeof val === 'object') {
      const iName = cap(name);
      buildInterface(iName, [val as Record<string, unknown>]);
      return iName;
    }
    return 'unknown';
  }

  function buildInterface(name: string, objs: Record<string, unknown>[]): void {
    if (seen.has(name)) return;
    seen.add(name);
    const allKeys = [...new Set(objs.flatMap((o) => Object.keys(o)))];
    const props: string[] = [];
    for (const key of allKeys) {
      const present = objs.filter((o) => key in o);
      const optional = present.length < objs.length ? '?' : '';
      const propType = getType(present[0][key], cap(key));
      const safeKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : `"${key}"`;
      props.push(`  ${safeKey}${optional}: ${propType};`);
    }
    interfaces.push(`interface ${name} {\n${props.join('\n')}\n}`);
  }

  const rootType = getType(value, rootName);
  const defs = interfaces.reverse().join('\n\n');
  if (rootType !== cap(rootName)) {
    return `type ${cap(rootName)} = ${rootType};\n\n${defs}`.trim();
  }
  return defs;
}

Uso in uno script di build Node.js:

import * as fs from 'fs';

const json = JSON.parse(fs.readFileSync('api-response.json', 'utf8'));
const typescript = jsonToTypeScript(json, 'UserResponse');
fs.writeFileSync('types/user.ts', typescript);

Metodo 3: quicktype (CLI e libreria)

quicktype è lo strumento open source più completo per generare tipi TypeScript da JSON. Gestisce casi limite che generatori semplici si lasciano sfuggire e supporta diversi formati di output.

npm install -g quicktype

# Da un file locale
quicktype api-response.json -o types/user.ts --lang typescript

# Da una URL
quicktype https://api.example.com/users/42 -o types/user.ts --lang typescript

# Da stdin
cat api-response.json | quicktype --lang typescript

quicktype genera anche funzioni di validazione a runtime usando librerie come io-ts o zod — utile quando devi validare i dati in ingresso, non solo tiparli staticamente.

Gestire i casi complicati

Campi nullable

Le API JSON spesso restituiscono null per valori mancanti. Un generatore che vede null produce null come tipo — ma quello che di solito vuoi è string | null:

// Generato (prima passata — troppo stretto)
interface Root {
  name: string;
  middleName: null;
}

// Quello che dovresti scrivere
interface Root {
  name: string;
  middleName: string | null;
}

Rivedi sempre i campi nullable a mano dopo la generazione. Se hai più risposte di esempio in cui un campo è a volte string e a volte null, un buon generatore inferirà string | null in automatico.

Opzionale vs nullable

TypeScript distingue tra field?: string (la chiave può essere assente) e field: string | null (la chiave è sempre presente, il valore può essere null):

interface Order {
  id: number;
  total: number;
  discount?: number;   // chiave assente in alcune risposte
  note: string | null; // chiave sempre presente, valore può essere null
}

I generatori che analizzano più campioni JSON inferiscono questa distinzione con precisione. I generatori a singolo campione segnano le chiavi assenti come opzionali ma non sanno distinguere una chiave mancante da un valore null.

Chiavi che non sono identificatori validi

Alcune chiavi JSON contengono caratteri non validi come nomi di proprietà TypeScript:

// JSON
{ "content-type": "application/json", "x-request-id": "abc123" }

// TypeScript generato — valido ma richiede notazione a parentesi quadre
interface Root {
  "content-type": string;
  "x-request-id": string;
}

// Accesso con notazione a parentesi quadre
obj["content-type"]

Se controlli l'API, preferisci chiavi in camelCase per evitare il problema del tutto. Se non puoi, la sintassi con proprietà tra virgolette è TypeScript valido e funziona correttamente.

Array di tipi misti

// JSON
{ "values": [1, "two", true, null] }

// Generato
interface Root {
  values: (number | string | boolean | null)[];
}

Tipi union come questo sono tecnicamente corretti, ma di solito segnalano un problema di design dell'API. Se lo vedi, verifica se il campo è davvero misto o se i dati di esempio sono fuorvianti.

Usare in sicurezza i tipi generati

Generare un'interface è solo il primo passo. L'interface è un contratto a compile-time — non valida nulla a runtime. response.json() restituisce any, e TypeScript ti lascia fare il cast verso la tua interface senza verifica effettiva.

Per API interne dove controlli entrambi i lati, di solito basta così. Per API esterne o dati forniti dall'utente, valida a runtime. L'approccio consigliato è Zod:

import { z } from 'zod';

const UserSchema = z.object({
  id: z.number(),
  name: z.string(),
  email: z.string().email(),
  active: z.boolean(),
  address: z.object({
    street: z.string(),
    city: z.string(),
    zip: z.string(),
  }),
  tags: z.array(z.string()),
  orders: z.array(z.object({
    id: z.number(),
    total: z.number(),
    status: z.string(),
  })),
});

type User = z.infer<typeof UserSchema>; // tipo TypeScript derivato dallo schema

const raw = await response.json();
const user = UserSchema.parse(raw); // lancia se la forma non corrisponde

L'approccio di Zod è il meglio dei due mondi: lo schema è l'unica fonte di verità sia per il tipo TypeScript sia per la validazione a runtime. Se l'API cambia in modo breaking, te ne accorgi nel punto in cui i dati entrano nel tuo sistema.

Oltre le interface: validatori e client tipizzati

Un'interface generata è un contratto a compile-time. Lo step successivo sono librerie che ti danno sia il tipo sia il controllo a runtime da un'unica fonte:

  • Zod e io-ts — definisci uno schema, derivane il tipo TypeScript e valida i dati in ingresso al confine. Se l'API cambia, la build (e i log degli errori) te lo dicono subito.
  • tRPC e wrapper di fetch tipizzati (per es. openapi-fetch) — la procedura del server o la definizione OpenAPI è la fonte di verità, e il client raccoglie tipi corrispondenti in automatico, senza passaggio JSON-verso-TS di mezzo.
  • ts-pattern e union discriminate — quando i tuoi dati hanno un campo tag (per es. { kind: "circle" | "square", ... }), il pattern matching esaustivo intercetta i casi mancati a compile-time.

Mantenere i tipi allineati all'API

I tipi generati hanno una data di scadenza. Le API cambiano — un campo viene rinominato, appare un nuovo campo obbligatorio, oppure un campo string diventa nullable. Le tue interface diventano obsolete in silenzio.

Genera dalla spec OpenAPI, non da risposte di esempio

Se l'API ha una definizione OpenAPI (Swagger), openapi-typescript genera i tipi direttamente dalla spec:

npm install -g openapi-typescript
openapi-typescript https://api.example.com/openapi.json -o types/api.ts

È molto più affidabile che generare da una risposta di esempio, perché la spec documenta ogni campo, comprese le opzionali, le varianti nullable e i valori enum.

Rigenera i tipi in CI

Aggiungi una fase alla tua pipeline CI che rigenera i tipi dalla spec o da un campione noto e valido, poi esegue tsc --noEmit per controllare le rotture. Se l'API cambia, la build fallisce prima che la modifica breaking arrivi in produzione.

Domande frequenti

Come converto un JSON in un'interface TypeScript?

Incolla il JSON in un convertitore online come lo strumento JSON-verso-TypeScript di fixjson per output istantaneo, esegui quicktype in uno step di build, o genera da una spec OpenAPI con openapi-typescript per codice di lunga durata.

Come si mappano i tipi JSON sui tipi TypeScript?

Stringhe → string, tutti i numeri → number (TypeScript non ha un tipo intero), booleani → boolean, oggetti → interface con nome, e gli array inferiscono il tipo dell'elemento. Vedi la tabella di mappatura sopra e i sei tipi di dato JSON.

Come gestisco i campi nullable o opzionali?

Usa field?: T quando la chiave può mancare e field: T | null quando la chiave è sempre presente ma il valore può essere null. I generatori che analizzano più campioni lo inferiscono; gli strumenti a singolo campione richiedono revisione manuale.

Le interface TypeScript validano il JSON a runtime?

No — le interface vengono cancellate a compile-time. Per la sicurezza a runtime su dati esterni, valida con Zod o descrivi la forma con JSON Schema.

Conclusione

Convertire JSON in interface TypeScript è qualcosa che ogni sviluppatore TypeScript fa regolarmente. L'approccio giusto dipende dal contesto: per un'integrazione una tantum veloce, un convertitore online ti fa partire in meno di un minuto. Per una pipeline di build, quicktype o uno script personalizzato automatizza il processo. Per codice di produzione di lunga durata, generare tipi da una spec OpenAPI e validare a runtime con Zod offre le garanzie più solide.

Qualsiasi metodo usi, l'interface generata è un punto di partenza — rivedi i campi nullable, aggiungi commenti per le proprietà non ovvie e stringi i vincoli dove le tue regole di business sono più rigide di quanto suggerisca il campione JSON. Tipi che riflettono accuratamente i dati rendono l'intera codebase più facile da mantenere.

Se il JSON con cui stai lavorando ha errori di sintassi prima ancora di provare a convertirlo, prima riparalo. Un generatore TypeScript — come qualsiasi parser JSON — richiede input sintatticamente valido.