Blog post
JSON in TypeScript: Type-Safe Parsing and Validation
Parse JSON safely in TypeScript with Zod, type guards, and generics. Stop using 'any' for JSON and get full type safety.
Shashank Jain
Author


Article
The Problem with JSON and TypeScript
TypeScript's JSON.parse returns any, giving up all type safety. Casting with as MyType is a lie — TypeScript trusts you, but the runtime data might not match at all. The right approach is runtime validation that produces a typed result.
// Dangerous — TypeScript believes you, but runtime can still crash
const data = JSON.parse(response) as User;
console.log(data.name.toUpperCase()); // Crashes if name is missing
// Safe — validate at runtime, get type from the schema
import { z } from 'zod';
const UserSchema = z.object({ name: z.string(), age: z.number() });
type User = z.infer<typeof UserSchema>;
const data: User = UserSchema.parse(JSON.parse(response)); // Throws if invalidZod: Runtime Validation with TypeScript Types
import { z } from 'zod';
const ProductSchema = z.object({
id: z.string().uuid(),
name: z.string().min(1).max(200),
price: z.number().positive(),
tags: z.array(z.string()).optional().default([]),
createdAt: z.string().datetime()
});
type Product = z.infer<typeof ProductSchema>;
// Equivalent to:
// type Product = {
// id: string;
// name: string;
// price: number;
// tags: string[];
// createdAt: string;
// }
// Safe parse — returns { success, data } instead of throwing
const result = ProductSchema.safeParse(rawJson);
if (result.success) {
console.log(result.data.name); // Fully typed
} else {
console.error(result.error.issues);
}Type Guards: Manually Written
interface User {
id: number;
name: string;
email: string;
}
function isUser(value: unknown): value is User {
return (
typeof value === 'object' &&
value !== null &&
typeof (value as User).id === 'number' &&
typeof (value as User).name === 'string' &&
typeof (value as User).email === 'string'
);
}
const parsed: unknown = JSON.parse(jsonStr);
if (isUser(parsed)) {
console.log(parsed.name); // TypeScript knows this is User
}Typing API Responses
async function fetchUser(id: number): Promise<User> {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const raw: unknown = await res.json();
return UserSchema.parse(raw); // Validates and types in one step
}
// Generic fetch wrapper
async function fetchTyped<T>(url: string, schema: z.ZodType<T>): Promise<T> {
const res = await fetch(url);
const raw: unknown = await res.json();
return schema.parse(raw);
}JSON.stringify with TypeScript
// TypeScript knows what you are serializing
const user: User = { id: 1, name: 'Alice', email: 'alice@example.com' };
const json: string = JSON.stringify(user);
// Custom replacer with type safety
const filtered = JSON.stringify(user, (key, value) => {
if (key === 'password') return undefined;
return value;
});FAQ
Why is JSON.parse typed as any in TypeScript?
Because TypeScript cannot know what structure a string contains at compile time. The parsed value could be anything. This is a deliberate design — it reminds you to validate the result.
Should I use Zod or io-ts or Yup?
Zod is the current community standard — best TypeScript integration, active development, clean API. io-ts is more functional/category-theory oriented. Yup is older and primarily for form validation. Default to Zod for new projects.
How do I handle deeply nested JSON types in TypeScript?
Use Zod's .object().nested() or recursive schemas. For unknown-depth JSON, use the JsonValue type from the type-fest package: type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue }.
What is the unknown type and why use it instead of any?
unknown is the type-safe version of any. You can assign anything to unknown, but you cannot use it without first narrowing the type. Use unknown for untyped data (JSON.parse results, API responses) instead of any.
Can I generate Zod schemas from TypeScript types?
Not automatically — types are erased at runtime. But you can generate TypeScript types from Zod schemas (z.infer), which is the recommended direction. If you need to go the other way, tools like ts-to-zod exist but have limitations.
Keep reading
Recent blogs

Jun 14, 2026
JSON in C#: System.Text.Json and Newtonsoft Complete Guide
Serialize and deserialize JSON in C# using System.Text.Json and Newtonsoft.Json with practical examples.

Jun 14, 2026
JSON to Markdown Table: Convert JSON Arrays Instantly
Convert JSON arrays to Markdown tables in JavaScript, Python, and with the free online tool.

Jun 14, 2026
JSON in TypeScript: Type-Safe Parsing and Validation
Stop using any for JSON in TypeScript — use Zod, type guards, and generics for fully type-safe parsing.