JSON to Zod Schema — TypeScript source
Generate Zod validation schemas from JSON. Infers z.string, z.number, z.boolean, z.object, z.array, z.null, and z.union for mixed arrays.
This is the TypeScript implementation — the same logic the interactive tool runs, in a shareable, citable form.
// Pure JSON → Zod converter - no React, no DOM, deterministic.
// Recursively infers z.* validators; mixed arrays become z.union. Never throws.
export interface Options {
rootName?: string;
}
export interface Result {
ok: boolean;
code: string;
error: string | null;
}
function isPlainObject(v: unknown): v is Record<string, unknown> {
return typeof v === 'object' && v !== null && !Array.isArray(v);
}
function sanitizeVarName(name: string): string {
const cleaned = name.replace(/[^A-Za-z0-9_$]/g, '').replace(/^[0-9]+/, (m) => '_'.repeat(m.length));
return cleaned || 'schema';
}
export function jsonToZod(jsonString: string, opts: Options = {}): Result {
let value: unknown;
try {
value = JSON.parse(jsonString);
} catch (e) {
return { ok: false, code: '', error: (e as Error).message };
}
try {
const rootName = sanitizeVarName(opts.rootName || 'Root');
return { ok: true, code: `const ${rootName} = ${inferZod(value, 2)};`, error: null };
} catch (e) {
return { ok: false, code: '', error: (e as Error).message };
}
}
/** Infer a Zod schema string for a value. indent = current indentation depth. */
export function inferZod(value: unknown, indent = 2): string {
if (value === null) return 'z.null()';
switch (typeof value) {
case 'string':
return 'z.string()';
case 'number':
return 'z.number()';
case 'boolean':
return 'z.boolean()';
}
if (Array.isArray(value)) {
if (value.length === 0) return 'z.array(z.unknown())';
const types = value.map((e) => inferZod(e, indent + 2));
const distinct = [...new Set(types)];
const inner = distinct.length === 1 ? distinct[0] : `z.union([\n${pad(types.join(',\n'), indent + 2)}\n${pad('', indent)}])`;
return `z.array(${inner})`;
}
if (isPlainObject(value)) {
const pad0 = ' '.repeat(indent);
const pad1 = ' '.repeat(indent + 2);
const entries = Object.entries(value);
if (entries.length === 0) return 'z.object({})';
const fields = entries.map(([k, v]) => `${pad1}${k}: ${inferZod(v, indent + 2)},`);
return `z.object({\n${fields.join('\n')}\n${pad0}})`;
}
return 'z.unknown()';
}
function pad(s: string, depth: number): string {
const p = ' '.repeat(depth);
return s
.split('\n')
.map((l) => (l.length ? p + l : l))
.join('\n');
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →