Any TypeScript project
Build a safe API client that uses type guards to validate unknown JSON responses.
1 interface User { kind: 'user'; id: string; name: string; email: string; } 2 interface Post { kind: 'post'; id: string; title: string; authorId: string; } 3 interface ApiError { kind: 'error'; code: number; message: string; } 4 type ApiResponse = User | Post | ApiError; 5 6 // Custom type guards 7 function isUser(val: unknown): val is User { 8 return ( 9 typeof val === 'object' && val !== null && 10 (val as any).kind === 'user' && 11 typeof (val as any).id === 'string' && 12 typeof (val as any).name === 'string' && 13 typeof (val as any).email === 'string' 14 ); 15 } 16 17 function isPost(val: unknown): val is Post { 18 return ( 19 typeof val === 'object' && val !== null && 20 (val as any).kind === 'post' && 21 typeof (val as any).id === 'string' && 22 typeof (val as any).title === 'string' && 23 typeof (val as any).authorId === 'string' 24 ); 25 } 26 27 function isApiError(val: unknown): val is ApiError { 28 return ( 29 typeof val === 'object' && val !== null && 30 (val as any).kind === 'error' && 31 typeof (val as any).code === 'number' 32 ); 33 } 34 35 // Exhaustiveness check 36 function assertNever(x: never): never { 37 throw new Error(`Unhandled response kind: ${JSON.stringify(x)}`); 38 } 39 40 // Parse and dispatch 41 function handleResponse(raw: unknown): string { 42 if (isUser(raw)) { 43 return `User: ${raw.name} (${raw.email})`; 44 } 45 if (isPost(raw)) { 46 return `Post: "${raw.title}" by author ${raw.authorId}`; 47 } 48 if (isApiError(raw)) { 49 return `Error ${raw.code}: ${raw.message}`; 50 } 51 throw new Error('Unknown response shape'); 52 } 53 54 // Discriminated union switch with exhaustiveness 55 function processResponse(response: ApiResponse): string { 56 switch (response.kind) { 57 case 'user': return `Welcome, ${response.name}!`; 58 case 'post': return `New post: ${response.title}`; 59 case 'error': return `Error: ${response.message}`; 60 default: return assertNever(response); // TS error if case missing 61 } 62 } 63 64 // Tests 65 console.log(handleResponse({ kind: 'user', id: '1', name: 'Alice', email: 'a@b.com' })); 66 console.log(handleResponse({ kind: 'post', id: '1', title: 'Hello', authorId: 'u-1' })); 67 console.log(handleResponse({ kind: 'error', code: 404, message: 'Not found' }));
parseApiResponse returns correctly typed User or Post, throws on unknown shapes
Sign in to share your feedback and join the discussion.