TypeScript JSON.parse: Type-Safe Parsing and Error Handling
JSON.parse in TypeScript returns 'any' by default. Learn how to add proper types, handle SyntaxError, and validate structure with Zod or type guards.
Have broken JSON right now? Fix it free in under 1 second — no signup.
Fix My JSON →JSON.parse in TypeScript returns any. That one word — any — is responsible for a surprising number of runtime bugs in TypeScript codebases. You parse an API response, TypeScript trusts you, and then your code fails at runtime because the structure didn't match what you expected. This guide covers every pattern for type-safe JSON parsing in TypeScript.
The Core Problem: JSON.parse Returns any
const data = JSON.parse('{"name":"Alice","age":30}');
// data has type: any
// TypeScript won't catch this bug at compile time:
console.log(data.naem.toUpperCase()); // runtime error: naem is undefined
The any return type means TypeScript opts out of type checking entirely. You lose all the benefits of the type system as soon as you call JSON.parse.
Basic Type Assertion (Not Safe, But Common)
The quickest approach is a type assertion:
interface User {
name: string;
age: number;
}
const data = JSON.parse(input) as User;
This compiles cleanly but provides no actual safety. If the parsed JSON doesn't match User, TypeScript won't tell you — you'll get a runtime error later when you try to use a field that doesn't exist. Use this only when you fully control the input and trust the source.
Type Guards: Safe Manual Validation
A type guard is a function that returns value is Type — a TypeScript predicate. If it returns true, TypeScript narrows the type in that branch.
interface User {
name: string;
age: number;
}
function isUser(value: unknown): value is User {
return (
typeof value === 'object' &&
value !== null &&
'name' in value &&
typeof (value as any).name === 'string' &&
'age' in value &&
typeof (value as any).age === 'number'
);
}
function parseUser(input: string): User {
const parsed: unknown = JSON.parse(input);
if (!isUser(parsed)) {
throw new Error('Invalid User structure');
}
return parsed; // TypeScript knows this is User here
}
Type guards are verbose for complex types but have zero dependencies and work at runtime.
Using unknown Instead of any
A better baseline: always assign JSON.parse output to unknown instead of any. This forces you to validate before using the value.
function safeParse(input: string): unknown {
try {
return JSON.parse(input);
} catch {
return null;
}
}
const result = safeParse(apiResponse);
// TypeScript forces you to narrow the type before using it:
if (typeof result === 'object' && result !== null && 'name' in result) {
// now you can access result.name
}
The difference between any and unknown: any lets you use the value without checks; unknown requires you to check before use.
Zod: Schema Validation with Inferred Types
Zod is the most popular TypeScript validation library. It solves theJSON.parse problem elegantly: define a schema, parse with the schema, and get a fully typed result with runtime validation.
import { z } from 'zod';
const UserSchema = z.object({
name: z.string(),
age: z.number(),
email: z.string().email().optional(),
});
// Infer the TypeScript type from the schema
type User = z.infer
function parseUser(input: string): User {
const raw = JSON.parse(input);
return UserSchema.parse(raw); // throws ZodError if invalid
}
// Or use safeParse to avoid throwing:
function tryParseUser(input: string): User | null {
try {
const raw = JSON.parse(input);
const result = UserSchema.safeParse(raw);
return result.success ? result.data : null;
} catch {
return null;
}
}
Zod's safeParse never throws. It returns { success: true, data: T } or { success: false, error: ZodError }. The error contains detailed information about which fields failed and why.
const AddressSchema = z.object({
street: z.string(),
city: z.string(),
zip: z.string().regex(/^\d{5}$/),
});
const UserSchema = z.object({
name: z.string(),
age: z.number().int().min(0).max(150),
address: AddressSchema.optional(),
tags: z.array(z.string()).default([]),
});
Handling SyntaxError from JSON.parse
JSON.parse throws a SyntaxError for invalid input. Always wrap it:
function safeParse
input: string,
validator: (value: unknown) => value is T
): T | null {
try {
const parsed = JSON.parse(input);
return validator(parsed) ? parsed : null;
} catch (e) {
if (e instanceof SyntaxError) {
console.error('Invalid JSON:', e.message);
}
return null;
}
}
For AI-generated JSON that may be malformed (markdown fences, trailing commas, Python booleans), fix it before parsing. AI JSONMedic provides an API endpoint that repairs the JSON first, then you parse the clean output:
async function parseAiJson
rawOutput: string,
validator: (v: unknown) => v is T
): Promise
const res = await fetch('https://aijsonmedic.com/api/repair', {
method: 'POST',
body: rawOutput,
});
const fixed = await res.text();
return safeParse(fixed, validator);
}
Generic Parse Utility
A reusable utility that combines all the above:
import { z, ZodSchema } from 'zod';
export function parseJson
input: string,
schema: ZodSchema
): { data: T; error: null } | { data: null; error: string } {
try {
const raw = JSON.parse(input);
const result = schema.safeParse(raw);
if (result.success) {
return { data: result.data, error: null };
}
return { data: null, error: result.error.message };
} catch (e) {
return { data: null, error: JSON parse failed: ${(e as Error).message} };
}
}
// Usage:
const { data, error } = parseJson(apiResponse, UserSchema);
if (error) {
console.error('Parse failed:', error);
} else {
console.log(data.name); // fully typed
}
JSON.parse with Reviver
The second argument to JSON.parse is a reviver function — useful for type transformations:
// Convert ISO date strings to Date objects
const data = JSON.parse(input, (key, value) => {
if (typeof value === 'string' && /^\d{4}-\d{2}-\d{2}T/.test(value)) {
return new Date(value);
}
return value;
});
Note: the reviver runs on every value in the tree. Be careful with performance on large objects.
Common Mistakes
Trusting JSON.parse return type:// Wrong — typescript thinks this is string but JSON.parse returns the parsed value
const name: string = JSON.parse('"Alice"');
const notAString: string = JSON.parse('42'); // TypeScript allows this, runtime: number
Using as unknown as Type to bypass checks:const data = JSON.parse(input) as unknown as User; // still no runtime validation
Forgetting that JSON.parse can throw:// Wrong — no try/catch, crashes on invalid input
const data = JSON.parse(userInput) as Config;
Summary
TypeScript's JSON.parse returns any, which disables type checking for the parsed value. The fix depends on your requirements:
- Minimum safety: use
unknowninstead ofany, add manual type guards - Production standard: use Zod for schema definition, validation, and type inference in one step
- Quick scripts: type assertions (
as Type) are acceptable when you control the input
For AI-generated JSON that may be malformed before it even reaches JSON.parse, the AI JSONMedic tool or its API handles repair automatically so you always start with valid JSON. To check a specific JSON value interactively during development, paste it into the JSON Validator — it reports the exact line, column, and a plain-English description of every error before you commit to writing Zod schemas or type guards.
For common JavaScript-specific parse errors and how to fix them, see Fix JavaScript JSON.parse Errors. If you're working with LLM output that leaks Python booleans or markdown fences into your TypeScript pipeline, the LLM JSON Repair Guide covers every AI failure pattern with TypeScript code fixes.
FAQ
How do I safely parse JSON in TypeScript without losing type safety?
Use JSON.parse() with unknown type, then validate with Zod: const data: unknown = JSON.parse(raw); const result = MySchema.safeParse(data); if (result.success) { / result.data is fully typed / }. Never cast directly with as MyType — it bypasses all runtime checks and will crash silently on unexpected shapes.
What is the difference between JSON.parse(s) as MyType and Zod parsing?
JSON.parse(s) as MyType is a TypeScript assertion — it tells the compiler to treat the value as MyType at compile time but adds zero runtime validation. If the actual data doesn't match MyType, TypeScript won't catch it and you'll get undefined access errors at runtime. Zod validates the shape at runtime and only returns the typed value if the data actually matches.
How do I handle nullable JSON values in TypeScript?
Use string | null (not string | undefined) for values that can be absent in JSON. undefined is not a valid JSON value — it gets dropped during JSON.stringify(). In Zod: z.string().nullable() for string | null, z.string().optional() for string | undefined (absent in the object, not null). For API responses, nullable() is usually correct.
Why does TypeScript not warn me when I access undefined keys from JSON.parse?
Because JSON.parse() returns any, and TypeScript doesn't check property access on any. This is by design — the compiler can't know the shape of arbitrary JSON at compile time. The fix is to type the result as unknown and run it through a validator (Zod, io-ts, or manual type guards) before accessing any properties.
How do I type a JSON API response in TypeScript without a schema library?
Write a type guard: function isUser(v: unknown): v is User { return typeof v === 'object' && v !== null && typeof (v as any).id === 'number' && typeof (v as any).name === 'string'; }. Then: if (isUser(data)) { data.id / typed / }. This is verbose for complex types — at 2+ properties, a schema library like Zod is faster to write and easier to maintain.
Still dealing with broken JSON?
Paste it in and get it fixed in under 1 second — free, no signup, no install. Works with ChatGPT, Claude, n8n, and any AI output.
Fix My JSON Free →Related Articles