Description
Paste any JSON (API response, config, database record) and get TypeScript interface or type definitions with smart type inference. Handles nested objects, mixed arrays, optional fields, nullable types, and common patterns like date strings and enums. Every frontend developer copies JSON from an API and needs types — this automates it.
Features
- Smart type inference:
- Primitives:
string, number, boolean, null
- Date detection: ISO 8601 strings →
string with /** @format date-time */ JSDoc comment
- URL detection:
string with /** @format uri */ annotation
- UUID detection:
string with /** @format uuid */ annotation
- Enum detection: when an array of objects has a field with few unique string values → string literal union
- Tuple detection: fixed-length arrays with different types →
[string, number, boolean]
- Array handling:
- Uniform arrays:
string[], User[]
- Mixed arrays:
(string | number)[]
- Array of objects: merge all objects' keys into one interface, mark keys not present in all objects as optional
- Nested objects → separate named interfaces with references
- Optional fields — When generating from an array of objects, fields missing in some objects become
fieldName?: type
- Nullable —
null values → fieldName: string | null
- Output options:
interface vs type alias
readonly modifier on all properties
- Optional semicolons vs no semicolons
- Root type name (default:
Root)
- Nested interface naming:
PascalCase from key name
- Export keyword toggle
- Indent: 2 spaces / 4 spaces / tabs
- Sort keys alphabetically (optional)
- Multiple roots — Paste a JSON array of objects → generates interface from merged shape
- Inline vs extracted — Nested objects as inline
{ ... } or extracted to separate named interfaces
- Copy / Download — Copy as
.ts content or download as .d.ts file
Implementation
Dependencies
Zero — JSON.parse() + recursive type analysis.
Core Logic
interface GeneratorOptions {
rootName: string;
style: 'interface' | 'type';
readonly: boolean;
semicolons: boolean;
exportKeyword: boolean;
indent: string;
sortKeys: boolean;
inlineNested: boolean;
}
interface TypeInfo {
kind: 'primitive' | 'array' | 'object' | 'union' | 'tuple' | 'literal';
value: string; // For primitives: 'string', 'number', etc.
properties?: Map<string, { type: TypeInfo; optional: boolean }>; // For objects
elementType?: TypeInfo; // For arrays
members?: TypeInfo[]; // For unions/tuples
interfaceName?: string; // For extracted nested interfaces
}
function inferType(value: unknown, keyHint?: string): TypeInfo {
if (value === null) return { kind: 'primitive', value: 'null' };
if (typeof value === 'string') return { kind: 'primitive', value: detectStringSubtype(value) };
if (typeof value === 'number') return { kind: 'primitive', value: Number.isInteger(value) ? 'number' : 'number' };
if (typeof value === 'boolean') return { kind: 'primitive', value: 'boolean' };
if (Array.isArray(value)) {
if (value.length === 0) return { kind: 'array', value: 'unknown[]', elementType: { kind: 'primitive', value: 'unknown' } };
const itemTypes = value.map(v => inferType(v));
// All same type → T[]
if (allSameType(itemTypes)) return { kind: 'array', value: '', elementType: itemTypes[0] };
// Array of objects → merge into one interface with optional fields
if (itemTypes.every(t => t.kind === 'object')) return { kind: 'array', value: '', elementType: mergeObjectTypes(itemTypes) };
// Mixed primitives → union array
const unique = deduplicateTypes(itemTypes);
if (unique.length <= 3) return { kind: 'array', value: '', elementType: { kind: 'union', value: '', members: unique } };
return { kind: 'array', value: 'unknown[]', elementType: { kind: 'primitive', value: 'unknown' } };
}
if (typeof value === 'object') {
const props = new Map<string, { type: TypeInfo; optional: boolean }>();
for (const [k, v] of Object.entries(value!)) {
props.set(k, { type: inferType(v, k), optional: false });
}
return { kind: 'object', value: '', properties: props };
}
return { kind: 'primitive', value: 'unknown' };
}
function mergeObjectTypes(types: TypeInfo[]): TypeInfo {
const allKeys = new Set(types.flatMap(t => [...(t.properties?.keys() ?? [])]));
const merged = new Map<string, { type: TypeInfo; optional: boolean }>();
for (const key of allKeys) {
const values = types.map(t => t.properties?.get(key)).filter(Boolean);
const optional = values.length < types.length; // Not in all objects
const mergedType = values.length === 1 ? values[0]!.type : mergeTypes(values.map(v => v!.type));
merged.set(key, { type: mergedType, optional });
}
return { kind: 'object', value: '', properties: merged };
}
function generateTypeScript(type: TypeInfo, options: GeneratorOptions): string {
const interfaces: string[] = [];
const rootType = typeToString(type, options.rootName, interfaces, options);
const keyword = options.style === 'interface' ? 'interface' : 'type';
const exp = options.exportKeyword ? 'export ' : '';
const eq = options.style === 'type' ? ' = ' : ' ';
// Root
if (type.kind === 'object') {
interfaces.push(`${exp}${keyword} ${options.rootName}${eq}${rootType}`);
} else {
interfaces.push(`${exp}type ${options.rootName} = ${rootType};`);
}
return interfaces.join('\n\n');
}
function detectStringSubtype(value: string): string {
if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/.test(value)) return 'string'; // Date, but still string type
return 'string';
}
Privacy
Pure client-side JSON parsing. API responses (which may contain sensitive data) never leave the browser.
UI
- Left panel: JSON input textarea with syntax highlighting
- Right panel: Generated TypeScript output with syntax highlighting and copy button
- Options bar (between panels):
- Root name input
interface / type toggle
readonly checkbox
export checkbox
- Sort keys checkbox
- Inline/extract nested toggle
- Indent selector
- Stats: Interface count, property count, detected patterns (dates, UUIDs, enums)
- Download:
.d.ts file
- Responsive — panels stack on mobile
Files to Create/Modify
| File |
Purpose |
tools/json-to-ts/package.json |
Workspace package |
tools/json-to-ts/src/tool.ts |
Type inference, merging, TypeScript generation |
tools/json-to-ts/src/page.ts |
Split-panel UI, options bar |
tools/json-to-ts/src/meta.ts |
ToolMeta |
tools/json-to-ts/src/index.ts |
Exports |
tools/json-to-ts/tests/tool.unit.test.ts |
Inference accuracy, merging, optional fields, edge cases |
tools/json-to-ts/tests/page.unit.test.ts |
DOM rendering tests |
src/index.ts |
Register route |
src/index.html |
Add to tool grid |
Description
Paste any JSON (API response, config, database record) and get TypeScript
interfaceortypedefinitions with smart type inference. Handles nested objects, mixed arrays, optional fields, nullable types, and common patterns like date strings and enums. Every frontend developer copies JSON from an API and needs types — this automates it.Features
string,number,boolean,nullstringwith/** @format date-time */JSDoc commentstringwith/** @format uri */annotationstringwith/** @format uuid */annotation[string, number, boolean]string[],User[](string | number)[]fieldName?: typenullvalues →fieldName: string | nullinterfacevstypealiasreadonlymodifier on all propertiesRoot)PascalCasefrom key name{ ... }or extracted to separate named interfaces.tscontent or download as.d.tsfileImplementation
Dependencies
Zero —
JSON.parse()+ recursive type analysis.Core Logic
Privacy
Pure client-side JSON parsing. API responses (which may contain sensitive data) never leave the browser.
UI
interface/typetogglereadonlycheckboxexportcheckbox.d.tsfileFiles to Create/Modify
tools/json-to-ts/package.jsontools/json-to-ts/src/tool.tstools/json-to-ts/src/page.tstools/json-to-ts/src/meta.tstools/json-to-ts/src/index.tstools/json-to-ts/tests/tool.unit.test.tstools/json-to-ts/tests/page.unit.test.tssrc/index.tssrc/index.html