JSON en TypeScript

Génère des interfaces TypeScript depuis JSON : clés optionnelles, unions de types mixtes, null distinct, tableaux fusionnés — dans le navigateur.

Entrée
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
}

Interfaces : 3

Des types déduits des données, pas devinés

Un document JSON ne porte pas ses propres types : il porte des valeurs, et les types doivent en être relus. C'est facile pour un seul objet et étonnamment subtil pour une collection : la forme que vous voulez n'est pas celle d'un enregistrement en particulier, mais celle que chaque enregistrement doit satisfaire. Cet outil déduit des interfaces TypeScript à partir d'un échantillon JSON en examinant tout ce que l'échantillon contient, de sorte que les types produits décrivent l'ensemble de vos données et non la première ligne qui se trouvait au-dessus.

La conversion ne va que dans un sens. Reconvertir les types en JSON reviendrait à inventer des valeurs, et le but est ici l'inverse : le document collé est la source de vérité, et chaque déclaration en découle. Collez une réponse d'API, un fichier de configuration ou une ligne de log, et lisez l'interface que vous auriez sinon écrite à la main.

Comment les tableaux sont fusionnés

Les décisions intéressantes se produisent toutes au niveau des tableaux. Un convertisseur naïf regarde le premier élément et s'arrête, ce qui fait paraître obligatoire tout champ qu'il voit par hasard et manque tout champ qu'il ne voit pas. Cet outil, au contraire, fusionne tous les éléments en un seul type, et trois choses en découlent :

  • Une clé présente dans certains éléments mais absente d'autres devient optionnelle, écrite avec un point d'interrogation. Si la moitié de vos enregistrements ont un "middleName" et la moitié non, le champ est "middleName?", ce qui est exactement ce qu'un consommateur doit gérer.
  • Une clé dont la valeur diffère en nature d'un élément à l'autre devient une union. Un champ qui est un nombre dans un enregistrement et une chaîne dans un autre est typé "number | string", non par souci d'ordre, mais parce que c'est ce que les données contiennent réellement et ce que votre code doit accepter.
  • Un objet imbriqué à l'intérieur des éléments est fusionné de la même façon, récursivement, et extrait dans sa propre interface. Dix éléments d'un tableau portant chacun un "address" produisent une seule interface Address décrivant les dix.

Lorsque le document est lui-même un tableau au niveau supérieur, la racine devient un alias vers une interface d'élément — par exemple "type Root = RootItem[]" — avec le type de l'élément fusionné écrit en dessous.

Null, optionnel, et pourquoi ils diffèrent

Il est tentant de traiter un null comme une clé absente, et c'est une erreur. En TypeScript, "name?: string" signifie que la propriété peut être absente ; "name: string | null" signifie qu'elle est toujours là mais peut contenir null. Ce sont des contrats différents, et un consommateur les vérifie différemment — "in" contre une comparaison de valeur. Cet outil les garde distincts : un null explicite dans les données devient un membre d'union "| null", placé en dernier pour que "string | null" se lise comme prévu, tandis qu'une clé simplement absente de certains enregistrements devient optionnelle. Un champ qui est les deux — null dans un enregistrement, absent d'un autre — ressort comme les deux, "field?: T | null", parce que les deux faits sont vrais de vos données.

Les objets imbriqués deviennent des interfaces nommées

Plutôt que d'incruster une forme imbriquée dans son parent, chaque objet est extrait dans sa propre interface dont le nom dérive de la clé sous laquelle il se trouve. Un objet "user" devient une interface User ; un "address" à l'intérieur devient une interface Address que User référence. Les types profondément incrustés sont difficiles à lire et impossibles à réutiliser, et les interfaces nommées sont ce que vous auriez écrit vous-même. Les éléments d'un tableau sont mis au singulier quand c'est possible — "users" donne un User, "categories" un Category — et une clé qui ne se pluralise pas reçoit le suffixe Item pour que l'élément ait un nom à lui.

Si deux objets différents prenaient le même nom — deux "data" sans rapport, par exemple — le second reçoit un suffixe plutôt que d'être fusionné, de sorte que des formes distinctes le restent. L'objet racine est émis en premier et vous pouvez le renommer ; le choix entre une sortie "interface" et "type" est une bascule, car certaines bases de code préfèrent les alias de type partout.

Le repli sur unknown

Certaines valeurs ne portent aucune information de type. Un tableau vide pourrait contenir n'importe quoi ; un objet vide n'a aucune clé à décrire. Plutôt que de recourir à "any" — qui désactive la vérification de types pour tout ce qui suit — l'outil se replie sur "unknown" : un tableau vide devient "unknown[]", un objet vide devient "Record<string, unknown>". La différence compte. "any" laisse passer les bugs en silence ; "unknown" oblige le consommateur à restreindre la valeur avant de l'utiliser, de sorte que le type déduit reste honnête sur ce que l'échantillon a dit et n'a pas dit.

Ce sont les types que vous resserreriez à la main dès que vous saurez ce que la collection vide est censée contenir — mais tant que les données ne le disent pas, "unknown" est la réponse honnête, et c'est celle qui garde le reste de vos types sûrs.

Sur quoi il tourne, et où

Tout se passe dans votre navigateur. Le JSON est analysé et les types sont déduits sur votre propre appareil ; rien de ce que vous collez n'est téléversé, stocké ni journalisé. Cela rend l'outil sûr à utiliser sur une vraie réponse d'API ou un fichier de configuration contenant des secrets : l'échantillon ne quitte jamais la page. La sortie est du TypeScript ordinaire que vous pouvez coller directement dans un fichier "d.ts" ou un module, ajuster les quelques champs "unknown" que les données n'ont pas pu décrire, et utiliser.

Questions fréquentes

Mon JSON est-il envoyé à un serveur ?
Non. Le document est analysé et les types sont déduits entièrement dans votre navigateur, et rien de ce que vous collez n'est téléversé ni journalisé. C'est sûr à utiliser sur une vraie réponse d'API ou un fichier de configuration.
Pourquoi un champ est-il optionnel alors qu'il est présent dans mon échantillon ?
Parce qu'il est absent d'au moins un élément d'un tableau que l'outil a fusionné. L'inférence lit tous les éléments, pas seulement le premier, donc une clé que certains enregistrements omettent devient optionnelle — c'est le type que vos données admettent réellement, même si l'enregistrement que vous avez regardé l'incluait par hasard.
Pourquoi un champ est-il devenu une union comme string | number ?
Parce que la valeur avait des natures différentes dans différents éléments du tableau — une chaîne dans un enregistrement et un nombre dans un autre. Le type fusionné doit accepter les deux, il est donc écrit comme une union. Si cela vous surprend, cela signifie généralement que les données sont moins uniformes que prévu, ce qui est bon à savoir.
Pourquoi l'outil utilise-t-il unknown plutôt que any ?
Pour les valeurs qu'il ne peut pas décrire — un tableau vide, un objet vide — "unknown" garde le résultat sûr en types, obligeant le consommateur à restreindre la valeur avant de l'utiliser, alors que "any" désactiverait la vérification de types. Vous pouvez resserrer ces champs à la main dès que vous saurez ce que la collection vide contient.
Quelle est la différence entre la sortie interface et type ?
Aucune dans les types qu'elles décrivent — les deux produisent les mêmes formes. "interface" est l'idiome courant pour les types objet et peut être étendu et fusionné ; les alias "type" sont ce que certaines bases de code préfèrent utiliser partout. La bascule est là pour que la sortie corresponde au style de votre projet.
Peut-il reconvertir du TypeScript en JSON ?
Non, et délibérément. La conversion est à sens unique : JSON en entrée, types en sortie. Aller dans l'autre sens reviendrait à inventer des valeurs qui n'ont jamais été dans vos données, et tout l'intérêt est que chaque déclaration découle de ce que vous avez réellement collé.
Comment les objets imbriqués sont-ils nommés ?
D'après la clé sous laquelle ils se trouvent : un objet "user" devient User, un "address" à l'intérieur devient Address. Les éléments d'un tableau sont mis au singulier quand c'est possible — "categories" donne Category — et une clé qui ne se pluralise pas reçoit le suffixe Item. Deux formes différentes qui entreraient en collision sur un nom reçoivent un suffixe plutôt que d'être fusionnées, de sorte qu'elles restent distinctes.