类型安全的鸿沟
TypeScript 的类型系统仅存在于编译时——JSON.parse() 返回 any,丢失了所有类型信息。这在期望和实际之间造成了危险的鸿沟。
interface User {
name: string;
age: number;
email?: string;
}
// 编译通过但运行时没有任何保证
const user: User = JSON.parse(jsonString);
// user 在运行时可以是任何东西!
类型守卫实现运行时安全
实现自定义类型守卫来验证 JSON 结构:
interface User {
name: string;
age: number;
email?: string;
}
function isUser(obj: unknown): obj is User {
return (
typeof obj === 'object' &&
obj !== null &&
typeof (obj as User).name === 'string' &&
typeof (obj as User).age === 'number' &&
((obj as User).email === undefined || typeof (obj as User).email === 'string')
);
}
// 安全解析与验证
function parseUser(json: string): User {
const parsed = JSON.parse(json);
if (!isUser(parsed)) {
throw new Error('无效的用户数据');
}
return parsed;
}
Zod:运行时验证与类型推断
Zod 同时提供运行时验证和类型推断:
import { z } from 'zod';
const UserSchema = z.object({
name: z.string().min(1),
age: z.number().int().positive(),
email: z.string().email().optional(),
tags: z.array(z.string()).default([]),
});
// 从 schema 推断 TypeScript 类型
type User = z.infer<typeof UserSchema>;
// 运行时验证
function parseUser(json: string): User {
const data = JSON.parse(json);
return UserSchema.parse(data); // 无效时抛出 ZodError
}
// 安全解析(返回结果对象)
const result = UserSchema.safeParse(data);
if (result.success) {
console.log(result.data); // 完整类型
} else {
console.error(result.error.issues);
}
泛型 JSON 解析器
创建可复用的类型安全 JSON 解析工具:
import { z, ZodType } from 'zod';
function createJsonParser<T>(schema: ZodType<T>) {
return {
parse: (json: string): T => schema.parse(JSON.parse(json)),
safeParse: (json: string) => {
try {
const data = JSON.parse(json);
return schema.safeParse(data);
} catch (e) {
return { success: false, error: new z.ZodError([{ message: '无效的 JSON', path: [], code: 'custom' }]) };
}
}
};
}
// 使用
const userParser = createJsonParser(UserSchema);
const user = userParser.parse(jsonString);
辨识联合类型
处理多态 JSON 结构:
const EventSchema = z.discriminatedUnion('type', [
z.object({
type: z.literal('user_created'),
payload: z.object({ userId: z.string(), name: z.string() }),
}),
z.object({
type: z.literal('order_placed'),
payload: z.object({ orderId: z.string(), amount: z.number() }),
}),
]);
type Event = z.infer<typeof EventSchema>;
function handleEvent(event: Event) {
switch (event.type) {
case 'user_created':
console.log(event.payload.userId); // 完整类型
break;
case 'order_placed':
console.log(event.payload.amount); // 完整类型
break;
}
}
高级模式
递归 Schema
const CategorySchema: z.ZodType<Category> = z.lazy(() =>
z.object({
name: z.string(),
children: z.array(CategorySchema).default([]),
})
);
interface Category {
name: string;
children: Category[];
}
转换与预处理
const UserSchema = z.object({
name: z.string(),
age: z.preprocess(
(val) => (typeof val === 'string' ? parseInt(val, 10) : val),
z.number().int().positive()
),
createdAt: z.preprocess(
(val) => (typeof val === 'string' ? new Date(val) : val),
z.date()
),
});
性能优化
- 缓存编译后的 schema — Zod schema 编译开销较大
- 使用 strict() 模式 — 尽早拒绝未知属性
- 批量验证 — 一次性验证数组,而非逐项
- 考虑 superjson — 处理复杂类型(Date、Map、Set)
import superjson from 'superjson';
const data = {
date: new Date(),
map: new Map([['key', 'value']]),
};
const { json, meta } = superjson.serialize(data);
// json: {"date":"2026-07-22T...","map":[["key","value"]]}
// meta: {"values":{"date":["Date"],"map":["Map"]}}
const restored = superjson.deserialize({ json, meta });
// 完美往返,保留类型信息
相关资源
- JSON 最佳实践 — 通用最佳实践指南
- JSON Schema 完全指南 — 验证你的 JSON 结构
- JSON 安全最佳实践 — 防范常见漏洞
- JSON 格式化工具 — 在线格式化和验证 JSON
- JSON Schema 验证器 — 测试你的 schema