JSON Processing in TypeScript: From Basic Parsing to Strict Typing
TypeScript, as a superset of JavaScript, inherits all built-in methods for working with JSON but adds a powerful type system. However, the standard JSON.parse() returns the any type, which negates all the benefits of static typing. In this article, we will explore how to safely and efficiently process JSON in TypeScript, using both native capabilities and specialized libraries.
The Problem with Standard JSON.parse
Consider a typical scenario:
const jsonString = '{"name":"Alice","age":30}';const user: any = JSON.parse(jsonString); // type anyconsole.log(user.name.toUpperCase()); // Runtime error if name is missingTypeScript will not warn you if the JSON structure does not match expectations. The solution is to use type assertion or, better yet, runtime validation.
Basic Type Assertion
The simplest way is to explicitly specify the result type:
interface User { id: number; name: string; email?: string;}
const raw = '{"id":1,"name":"Bob"}';const user = JSON.parse(raw) as User;console.log(user.name); // TypeScript knows this is a stringDrawback: if the JSON arrives with different fields, an error will only occur at runtime. This is unsafe when working with external APIs.
Recursive and Conditional Types for JSON
TypeScript allows you to describe any JSON using a recursive type:
type JSONValue = | string | number | boolean | null | JSONValue[] | { [key: string]: JSONValue };
function safeParse(json: string): JSONValue | Error { try { return JSON.parse(json) as JSONValue; } catch (e) { return new Error('Invalid JSON'); }}This approach guarantees that the result will always be a valid JSON structure, but it does not check the specific shape.
Zod Library — Runtime Schema Validation
Zod is the most popular library for declarative data validation in TypeScript. It allows you to describe a schema and automatically derive a type from it.
Installation
npm install zodUsage Example
import { z } from 'zod';
// Describe the schemaconst UserSchema = z.object({ id: z.number().positive(), name: z.string().min(1), email: z.string().email().optional(), role: z.enum(['admin', 'user']).default('user'),});
// Infer the type from the schematype User = z.infer<typeof UserSchema>;
// Safe parsingconst result = UserSchema.safeParse(JSON.parse(rawJson));
if (result.success) { // result.data has type User console.log(result.data.name);} else { console.error(result.error.format());}Zod automatically checks types, field requiredness, default values, and returns detailed errors. It is an ideal choice for processing data from REST APIs or configuration files.
io-ts Library — Functional Approach
io-ts is based on functional programming and the fp-ts library. It uses codec composition for decoding/encoding data.
Example
import * as t from 'io-ts';import { isRight } from 'fp-ts/lib/Either';
const UserCodec = t.type({ id: t.number, name: t.string, tags: t.array(t.string),});
const decoded = UserCodec.decode(JSON.parse(raw));
if (isRight(decoded)) { // decoded.right — typed object console.log(decoded.right.name);} else { // decoded.left — validation errors console.log(decoded.left);}io-ts is more strict and mathematically correct but requires an understanding of functional concepts (Either, pipe).