JSON Processing in TypeScript: From Basic Operations to Advanced Typing
JSON (JavaScript Object Notation) remains the primary data exchange format in web development. TypeScript, being a strictly typed superset of JavaScript, provides powerful tools for safe and efficient work with JSON. In this article, we will cover all aspects of JSON processing in TypeScript: from simple parsing to complex validation using generics and third-party libraries.
1. Basic Operations: Parsing and Serialization
TypeScript fully inherits the built-in JavaScript methods for working with JSON: JSON.parse() and JSON.stringify(). However, their use in TypeScript requires special attention to types.
Parsing a JSON String
// Simple parsing — result is anyconst rawData = JSON.parse('{"name": "Alice", "age": 30}');// rawData has type any — we lose all TypeScript advantages
// Correct approach: specify the expected type via a genericinterface User { name: string; age: number;}
const user: User = JSON.parse('{"name": "Alice", "age": 30}');// user is now typed, but TypeScript does not check conformance at runtimeImportant: JSON.parse() does not verify whether the passed string matches the specified interface. This can lead to runtime errors. Use additional tools for reliable validation.
Serializing Objects to JSON
const user: User = { name: "Bob", age: 25 };const jsonString = JSON.stringify(user);// Result: '{"name":"Bob","age":25}'
// Custom serialization with a replacerconst filteredJson = JSON.stringify(user, ['name']);// Result: '{"name":"Bob"}'
// Formatting with indentationconst prettyJson = JSON.stringify(user, null, 2);/* Result:{ "name": "Bob", "age": 25}*/2. Typing JSON Data: Interfaces and Types
Proper typing is a key advantage of TypeScript when working with JSON. Let's look at the main approaches.
Interfaces for Data Structures
interface ApiResponse<T> { status: 'success' | 'error'; data: T; message?: string; timestamp: number;}
interface Product { id: string; title: string; price: number; tags: string[]; inStock: boolean;}
// Using a generic to type an API responsetype ProductResponse = ApiResponse<Product>;
const response: ProductResponse = { status: 'success', data: { id: 'prod-123', title: 'Wireless Mouse', price: 29.99, tags: ['electronics', 'accessories'], inStock: true }, timestamp: Date.now()};Types for Dynamic JSON Structures
// For objects with unknown keystype DynamicObject = Record<string, unknown>;
// For strictly typed dictionariesinterface Config { [key: string]: string | number | boolean;}
// Union types for polymorphic JSONtype Shape = | { kind: 'circle'; radius: number } | { kind: 'rectangle'; width: number; height: number } | { kind: 'triangle'; base: number; height: number };
function calculateArea(shape: Shape): number { switch (shape.kind) { case 'circle': return Math.PI * shape.radius ** 2; case 'rectangle': return shape.width * shape.height; case 'triangle': return (shape.base * shape.height) / 2; }}3. JSON Data Validation: Runtime Checks
TypeScript only checks types at compile time. For runtime validation of JSON data (e.g., from an external API), use specialized libraries.
Using Zod for Declarative Validation
import { z } from 'zod';
// Define a schemaconst UserSchema = z.object({ name: z.string().min(2).max(50), age: z.number().int().positive().max(120), email: z