Generating TypeScript Types from JSON Data - A Custom Solution
typescript
Introduction
I wanted an easy way to generate TypeScript types from JSON data fetched from a CMS. There are a lot of libraries out there, like quicktype and json-schema-to-typescript, but they just didn’t work for me. So, I decided to create my own solution. In this article, I’ll show you the code I came up with to automatically generate TypeScript types from a JSON file.
The JSON File
1{2 title: 'test',3 id: 1111,4 startDate: '2024-07-02T15:00:00.000Z',5 endDate: '2024-07-29T17:01:00.000Z',6 thumbnailImage: null,7}
Approach 1 : Using Quicktype
The Code
I used the code snippet from the official documentation
The Result 🤷
1export interface Test {2 any3}
Approach 2: Using json-schema-to-typescript
The Code
1import { compile, compileFromFile } from 'json-schema-to-typescript'23if (!fs.existsSync('./src/libs/aem/types/type.d.ts')) {4 compile(data, 'Schema').then((ts) => {5 fs.writeFileSync('./src/type.d.ts', ts)6 })7}
The Result 🤷 (Another Fail)
1export interface Test {2 [k: string]: unknown;3}
I'm not sure if the problem is because my JSON data has fields that aren't filled by the user (resulting in null
) or if there's something wrong with my code. I got tired of trying to find the bug and just decided to write it myself instead.
Approach 3 : Writing my own code
The Code
1import * as fs from 'fs'2import path from 'path'34type JSONValue = string | number | boolean | JSONObject | JSONArray | unknown56export interface JSONObject {7 [key: string]: JSONValue8}910interface JSONArray extends Array<JSONValue> {}1112function generateTypeScriptType(13 name: string,14 json: JSONObject | JSONArray15): string {16 let result = ''1718 if (Array.isArray(json)) {19 result += `type ${name} = ${getType(json)}[];\n`20 } else {21 result += `interface ${name} {\n`22 for (const key in json) {23 if (json[key]) {24 result += ` ${key}: ${getType(json[key])};\n`25 }26 }27 result += `}\n`28 }2930 return result31}3233function getType(value: JSONValue): string {34 if (Array.isArray(value)) {35 return value.length > 0 ? `${getType(value[0])}[]` : 'unknown[]'36 }3738 if (value === null) {39 return 'null'40 }4142 switch (typeof value) {43 case 'object':44 return `{\n${Object.entries(value)45 .map(([key, val]) => ` ${key}: ${getType(val)};`)46 .join('\n')}\n}`47 case 'string':48 return 'string'49 case 'number':50 return 'number'51 case 'boolean':52 return 'boolean'53 default:54 return 'unknown'55 }56}5758function capitalizeFirstLetter(string: string) {59 if (typeof string !== 'string' || string.length === 0) {60 return string61 }62 return string.charAt(0).toUpperCase() + string.slice(1)63}6465type Props = {66 typeName: string67 filename: string68 data: JSONObject | JSONArray69}7071export function generateTypeFromJson({ typeName, filename, data }: Props) {72 const typeDefinitions = generateTypeScriptType(73 capitalizeFirstLetter(typeName),74 data75 )76 const filePath = path.resolve(77 'src/libs/aem/generateType/',78 `${filename.toLowerCase()}.d.ts`79 )8081 if (!fs.existsSync(filePath)) {82 fs.writeFileSync(filePath, typeDefinitions)83 } else {84 const currentContent = fs.readFileSync(filePath, 'utf8')85 if (currentContent !== typeDefinitions) {86 fs.writeFileSync(filePath, typeDefinitions)87 }88 }89}
Usage
1const data = await functionThatFetchesData()23if (data) {4 generateTypeFromJson({5 typeName: 'Test',6 filename: 'test',7 data: data,8 })9}
The Result 🎉
1interface Test {2 title: string;3 id: number;4 startDate: string;5 endDate: string;6 thumbnailImage: null,7}