| id |
schema-optional-fields |
| title |
Optional and Nullable Fields |
| category |
objects |
| skillLevel |
beginner |
| tags |
schema |
optional |
nullable |
undefined |
|
| lessonOrder |
27 |
| rule |
| description |
Optional and Nullable Fields using Schema. |
|
| summary |
Not all fields are required. Some are optional (may be missing), some are nullable (present but null), some accept both. You need to express these different optionality patterns while keeping types... |
Not all fields are required. Some are optional (may be missing), some are nullable (present but null), some accept both. You need to express these different optionality patterns while keeping types accurate.
import { Schema } from "effect"
// ============================================
// OPTIONAL FIELDS (may be missing)
// ============================================
const UserProfile = Schema.Struct({
id: Schema.String,
name: Schema.String,
// These fields may be omitted entirely
bio: Schema.optional(Schema.String),
website: Schema.optional(Schema.String),
age: Schema.optional(Schema.Number),
})
type UserProfile = typeof UserProfile.Type
// { id: string; name: string; bio?: string; website?: string; age?: number }
const decodeProfile = Schema.decodeUnknownSync(UserProfile)
// Minimal required fields only
const minimal = decodeProfile({
id: "user_123",
name: "Alice",
})
console.log(`✅ ${minimal.name}, bio: ${minimal.bio ?? "not set"}`)
// With optional fields
const full = decodeProfile({
id: "user_456",
name: "Bob",
bio: "Software developer",
website: "https://bob.dev",
age: 28,
})
console.log(`✅ ${full.name}, ${full.age} years old`)
// ============================================
// NULLABLE FIELDS (present but null)
// ============================================
const DatabaseRecord = Schema.Struct({
id: Schema.String,
// Field exists but value may be null
deletedAt: Schema.NullOr(Schema.Date),
parentId: Schema.NullOr(Schema.String),
})
type DatabaseRecord = typeof DatabaseRecord.Type
// { id: string; deletedAt: Date | null; parentId: string | null }
const decodeRecord = Schema.decodeUnknownSync(DatabaseRecord)
const activeRecord = decodeRecord({
id: "rec_123",
deletedAt: null,
parentId: "rec_100",
})
console.log(`✅ Record ${activeRecord.id}, deleted: ${activeRecord.deletedAt === null}`)
const deletedRecord = decodeRecord({
id: "rec_456",
deletedAt: new Date(),
parentId: null,
})
console.log(`✅ Record deleted at: ${deletedRecord.deletedAt?.toISOString()}`)
// ============================================
// OPTIONAL + NULLABLE (both)
// ============================================
const FlexibleProfile = Schema.Struct({
id: Schema.String,
// May be missing OR present as null
nickname: Schema.optional(Schema.NullOr(Schema.String)),
})
type FlexibleProfile = typeof FlexibleProfile.Type
// { id: string; nickname?: string | null }
const decodeFlexible = Schema.decodeUnknownSync(FlexibleProfile)
// Field missing
const noNickname = decodeFlexible({ id: "1" })
console.log(`Nickname: ${noNickname.nickname}`) // undefined
// Field is null
const nullNickname = decodeFlexible({ id: "2", nickname: null })
console.log(`Nickname: ${nullNickname.nickname}`) // null
// Field has value
const hasNickname = decodeFlexible({ id: "3", nickname: "Ace" })
console.log(`Nickname: ${hasNickname.nickname}`) // "Ace"
// ============================================
// OPTIONAL WITH DEFAULTS
// ============================================
const Settings = Schema.Struct({
theme: Schema.optionalWith(Schema.String, { default: () => "light" }),
pageSize: Schema.optionalWith(Schema.Number, { default: () => 20 }),
notifications: Schema.optionalWith(Schema.Boolean, { default: () => true }),
})
type Settings = typeof Settings.Type
const decodeSettings = Schema.decodeUnknownSync(Settings)
const defaultSettings = decodeSettings({})
console.log(`✅ Theme: ${defaultSettings.theme}, Page: ${defaultSettings.pageSize}`)
// Output: ✅ Theme: light, Page: 20
const customSettings = decodeSettings({ theme: "dark", pageSize: 50 })
console.log(`✅ Theme: ${customSettings.theme}, Page: ${customSettings.pageSize}`)
// Output: ✅ Theme: dark, Page: 50
| Pattern |
Type |
Use Case |
| optional(S) |
T | undefined |
Field may be omitted |
| NullOr(S) |
T | null |
Field present but nullable |
| optional(NullOr(S)) |
T | null | undefined |
Both patterns |
| optionalWith(S, {default}) |
T |
Default when missing |
- User profiles with optional info
- Database records with nullable columns
- API responses with optional fields
- Config with sensible defaults