| title |
Parse JSON Responses Safely |
| id |
http-json-responses |
| skillLevel |
beginner |
| applicationPatternId |
making-http-requests |
| summary |
Use Effect Schema to validate and parse HTTP JSON responses with type safety. |
| tags |
http |
json |
schema |
validation |
getting-started |
|
| rule |
| description |
Always validate HTTP responses with Schema to catch API changes at runtime. |
|
| author |
PaulJPhilp |
| related |
http-hello-world |
schema-hello-world |
|
| lessonOrder |
1 |
Use Effect Schema to validate HTTP JSON responses, ensuring the data matches your expected types at runtime.
APIs can change without warning:
- Fields disappear - Backend removes a field
- Types change - String becomes number
- Nulls appear - Required field becomes optional
- New fields - Extra data you didn't expect
Schema validation catches these issues immediately.
import { Effect, Console } from "effect"
import { Schema } from "effect"
import { HttpClient, HttpClientRequest, HttpClientResponse } from "@effect/platform"
import { NodeHttpClient, NodeRuntime } from "@effect/platform-node"
// ============================================
// 1. Define response schemas
// ============================================
const PostSchema = Schema.Struct({
id: Schema.Number,
title: Schema.String,
body: Schema.String,
userId: Schema.Number,
})
type Post = Schema.Schema.Type<typeof PostSchema>
const PostArraySchema = Schema.Array(PostSchema)
// ============================================
// 2. Fetch and validate single item
// ============================================
const getPost = (id: number) =>
Effect.gen(function* () {
const client = yield* HttpClient.HttpClient
const response = yield* client.get(
`https://jsonplaceholder.typicode.com/posts/${id}`
)
const json = yield* HttpClientResponse.json(response)
// Validate against schema - fails if data doesn't match
const post = yield* Schema.decodeUnknown(PostSchema)(json)
return post
})
// ============================================
// 3. Fetch and validate array
// ============================================
const getPosts = Effect.gen(function* () {
const client = yield* HttpClient.HttpClient
const response = yield* client.get(
"https://jsonplaceholder.typicode.com/posts"
)
const json = yield* HttpClientResponse.json(response)
// Validate array of posts
const posts = yield* Schema.decodeUnknown(PostArraySchema)(json)
return posts
})
// ============================================
// 4. Handle validation errors
// ============================================
const safeGetPost = (id: number) =>
getPost(id).pipe(
Effect.catchTag("ParseError", (error) =>
Effect.gen(function* () {
yield* Console.error(`Invalid response format: ${error.message}`)
// Return a default or fail differently
return yield* Effect.fail(new Error(`Post ${id} has invalid format`))
})
)
)
// ============================================
// 5. Schema with optional fields
// ============================================
const UserSchema = Schema.Struct({
id: Schema.Number,
name: Schema.String,
email: Schema.String,
phone: Schema.optional(Schema.String), // May not exist
website: Schema.optional(Schema.String),
company: Schema.optional(
Schema.Struct({
name: Schema.String,
catchPhrase: Schema.optional(Schema.String),
})
),
})
const getUser = (id: number) =>
Effect.gen(function* () {
const client = yield* HttpClient.HttpClient
const response = yield* client.get(
`https://jsonplaceholder.typicode.com/users/${id}`
)
const json = yield* HttpClientResponse.json(response)
return yield* Schema.decodeUnknown(UserSchema)(json)
})
// ============================================
// 6. Run examples
// ============================================
const program = Effect.gen(function* () {
yield* Console.log("=== Validated Single Post ===")
const post = yield* getPost(1)
yield* Console.log(`Title: ${post.title}`)
yield* Console.log("\n=== Validated Posts Array ===")
const posts = yield* getPosts
yield* Console.log(`Fetched ${posts.length} posts`)
yield* Console.log("\n=== User with Optional Fields ===")
const user = yield* getUser(1)
yield* Console.log(`User: ${user.name}`)
yield* Console.log(`Company: ${user.company?.name ?? "N/A"}`)
})
program.pipe(
Effect.provide(NodeHttpClient.layer),
NodeRuntime.runMain
)
| Schema |
Purpose |
Schema.String |
String value |
Schema.Number |
Number value |
Schema.Boolean |
Boolean value |
Schema.Struct({...}) |
Object with fields |
Schema.Array(schema) |
Array of items |
Schema.optional(schema) |
May be undefined |
Schema.NullOr(schema) |
May be null |
Effect.catchTag("ParseError", (error) => {
// error.message contains validation details
// error.actual contains the invalid data
})
- Define schemas upfront - Document expected API format
- Use optional for unreliable fields - APIs change
- Handle ParseError - Graceful degradation
- Log validation failures - Debug API issues