JSON Processing in TypeScript: From Basic Operations to Advanced Typing
JSON (JavaScript Object Notation) remains the primary data exchange format in web development. TypeScript adds a powerful type system to working with JSON, which helps avoid many errors at the compilation stage. In this article, we will cover all aspects of JSON processing in TypeScript: from simple parsing to creating strictly typed validators.
Basic Parsing and Serialization
Let's start with fundamental operations. The built-in methods JSON.parse() and JSON.stringify() work in TypeScript the same way as in JavaScript, but with important differences in typing.
// Simple parsing — the result will be anyconst data: any = JSON.parse('{"name": "Alice", "age": 30}');
// Better to specify the type immediatelyinterface User { name: string; age: number;}
const user: User = JSON.parse('{"name": "Alice", "age": 30}') as User;console.log(user.name); // AliceIt's important to understand: as User is a type assertion, not a validation. If the JSON contains invalid data, TypeScript will not warn you about the error. Validation is needed for safe operation.
Safe Parsing with Generics
Let's create a universal function for safe JSON parsing with structure validation:
function safeParse<T>(json: string, validator: (data: unknown) => data is T): T | null { try { const parsed: unknown = JSON.parse(json); if (validator(parsed)) { return parsed; } return null; } catch { return null; }}
// Define the type and validator functioninterface Product { id: number; title: string; price: number;}
function isProduct(data: unknown): data is Product { if (typeof data !== 'object' || data === null) return false; const obj = data as Record<string, unknown>; return ( typeof obj.id === 'number' && typeof obj.title === 'string' && typeof obj.price === 'number' );}
const jsonString = '{"id": 1, "title": "Book", "price": 19.99}';const product = safeParse<Product>(jsonString, isProduct);if (product) { console.log(`Product: ${product.title}, price: ${product.price}`);} else { console.log('Invalid JSON format');}Working with Nested Structures and Arrays
Real JSON data often contains nested objects and arrays. Let's look at an example with typing a complex structure:
interface Order { id: string; customer: { name: string; email: string; }; items: Array<{ productId: number; quantity: number; }>; total: number;}
// Parse and immediately check the structurefunction parseOrder(json: string): Order { const raw = JSON.parse(json); // Simple validation if (!raw.id || !raw.customer || !Array.isArray(raw.items)) { throw new Error('Invalid order structure'); } return raw as Order;}
const orderJson = `{ "id": "ORD-001", "customer": {"name": "Bob", "email": "bob@example.com"}, "items": [{"productId": 42, "quantity": 2}], "total": 59.98}`;
try { const order = parseOrder(orderJson); console.log(`Order ${order.id} for a total of ${order.total}`);} catch (e) { console.error('Parsing error:', e.message);}Serialization with Custom Transformers
Sometimes you need to control how objects are converted to JSON. TypeScript allows creating custom serializers:
interface Config { host: string; port: number; timeout: number; // in milliseconds}
function serializeConfig(config: Config): string { return JSON.stringify({ host: config.host, port: config.port, timeout: config.timeout / 1000 // convert to seconds }, null, 2); // formatting with indentation}
const myConfig: Config = { host: 'localhost'