TypeScript JSON 序列化完全指南

TypeScript 阅读约 15 分钟
TypeScriptJSON序列化类型安全Zod

掌握 TypeScript 中带类型安全的 JSON 序列化技术。学习类型安全的解析、泛型工具、使用 Zod 进行运行时校验以及 TypeScript 中处理 JSON 的高级模式。

类型安全的鸿沟

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()
  ),
});

性能优化

  1. 缓存编译后的 schema — Zod schema 编译开销较大
  2. 使用 strict() 模式 — 尽早拒绝未知属性
  3. 批量验证 — 一次性验证数组,而非逐项
  4. 考虑 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 API 对比

语言 序列化 反序列化 美化输出 配置方式
Python json(内置) json.dumps(obj) json.loads(str) json.dumps(obj, indent=2) ensure_ascii, default, cls
JavaScript JSON(内置) JSON.stringify(obj) JSON.parse(str) JSON.stringify(obj, null, 2) replacer, space
Java Jackson / Gson mapper.writeValueAsString(obj) mapper.readValue(str, Class.class) mapper.writerWithDefaultPrettyPrinter() 注解、Module、Feature
Go encoding/json(内置) json.Marshal(obj) json.Unmarshal(data, &obj) json.MarshalIndent(obj, "", " ") struct tag
C# System.Text.Json(内置) JsonSerializer.Serialize(obj) JsonSerializer.Deserialize<T>(str) WriteIndented = true JsonSerializerOptions
TypeScript JSON(内置) JSON.stringify(obj) JSON.parse(str) JSON.stringify(obj, null, 2) replacer, space
Rust serde_json serde_json::to_string(&obj) serde_json::from_str::<T>(str) serde_json::to_string_pretty(&obj) serde attributes, custom (de)serializers

常见问题 (FAQ)

JSON 序列化时如何处理日期时间类型?

大多数语言的 JSON 库默认不支持日期时间类型。通常做法是序列化为 ISO 8601 格式字符串(如 "2026-07-15T10:30:00Z")或 Unix 时间戳,反序列化时再转换回日期时间对象。

如何忽略 JSON 序列化中的 null 值或空字段?

不同语言的实现方式不同:Python 可在自定义编码器中过滤 None 值;JavaScript 可使用 replacer 函数;Java Jackson 用 @JsonInclude 注解;Go 使用 omitempty struct tag;C# 设置 DefaultIgnoreCondition。

JSON 序列化时如何处理循环引用?

循环引用是 JSON 序列化的常见陷阱。标准 JSON 库通常会抛出异常或栈溢出。解决方案包括:使用 @JsonIdentityInfo 注解(Java Jackson)、实现自定义序列化器、将对象图转换为 DTO 后再序列化。

为什么我的私有字段没有被序列化?

JSON 序列化库通常只序列化公开(public)字段或带有 getter 的属性。Python 的 json 模块默认只序列化 dict 的公共键;Java Jackson 可通过 @JsonInclude 注解包含非公共成员。

JSON 序列化性能有哪些优化技巧?

1) 复用序列化器实例;2) 对于大型数据,使用流式 API;3) 使用编译时生成(C# Source Generator、Go easyjson);4) 避免过多嵌套层次;5) 使用数据库分页或分块传输大 JSON。

推荐工具

以下工具可以帮助你在 TypeScript 开发中更高效地处理 JSON 数据:

相关文章