89 lines
2.2 KiB
Markdown
89 lines
2.2 KiB
Markdown
---
|
|
title: Use z.unknown() Instead of z.any()
|
|
impact: CRITICAL
|
|
impactDescription: z.any() bypasses TypeScript's type system entirely; z.unknown() forces type narrowing before use
|
|
tags: schema, unknown, any, type-safety
|
|
---
|
|
|
|
## Use z.unknown() Instead of z.any()
|
|
|
|
`z.any()` infers to `any` type, disabling TypeScript's type checking for that value. `z.unknown()` infers to `unknown`, which forces you to narrow the type before using it. This preserves type safety while still allowing any input.
|
|
|
|
**Incorrect (using z.any):**
|
|
|
|
```typescript
|
|
import { z } from 'zod'
|
|
|
|
const eventSchema = z.object({
|
|
type: z.string(),
|
|
payload: z.any(), // Infers to 'any'
|
|
})
|
|
|
|
type Event = z.infer<typeof eventSchema>
|
|
// { type: string; payload: any }
|
|
|
|
function handleEvent(event: Event) {
|
|
// No type error - TypeScript allows anything
|
|
console.log(event.payload.foo.bar.baz) // Runtime crash if structure is wrong
|
|
}
|
|
```
|
|
|
|
**Correct (using z.unknown):**
|
|
|
|
```typescript
|
|
import { z } from 'zod'
|
|
|
|
const eventSchema = z.object({
|
|
type: z.string(),
|
|
payload: z.unknown(), // Infers to 'unknown'
|
|
})
|
|
|
|
type Event = z.infer<typeof eventSchema>
|
|
// { type: string; payload: unknown }
|
|
|
|
function handleEvent(event: Event) {
|
|
// TypeScript error: Object is of type 'unknown'
|
|
console.log(event.payload.foo) // Won't compile
|
|
|
|
// Must narrow type first
|
|
if (typeof event.payload === 'object' && event.payload !== null) {
|
|
// Now TypeScript knows it's an object
|
|
}
|
|
}
|
|
```
|
|
|
|
**Better approach with discriminated unions:**
|
|
|
|
```typescript
|
|
import { z } from 'zod'
|
|
|
|
const userCreatedSchema = z.object({
|
|
type: z.literal('user.created'),
|
|
payload: z.object({
|
|
userId: z.string(),
|
|
email: z.string().email(),
|
|
}),
|
|
})
|
|
|
|
const orderPlacedSchema = z.object({
|
|
type: z.literal('order.placed'),
|
|
payload: z.object({
|
|
orderId: z.string(),
|
|
amount: z.number(),
|
|
}),
|
|
})
|
|
|
|
const eventSchema = z.discriminatedUnion('type', [
|
|
userCreatedSchema,
|
|
orderPlacedSchema,
|
|
])
|
|
|
|
// Full type safety for each event type
|
|
```
|
|
|
|
**When NOT to use this pattern:**
|
|
- When you're consuming a third-party API where you truly don't know the shape
|
|
- When prototyping and will add proper types later
|
|
|
|
Reference: [Zod API - unknown](https://zod.dev/api#unknown)
|