- 开发工具
【免费下载链接】superjson
Safely serialize JavaScript expressions to a superset of JSON, which includes Dates, BigInts, and more.
Superjson 是一个轻量级的 JavaScript 序列化库,它把JSON.stringify/JSON.parse无法直接处理的Date、BigInt、Map、Set、RegExp、undefined、Error、URL、Symbol以及自定义类实例等类型,统一打包为「标准的 JSON 兼容输出 + 类型元数据(meta)」结构,从而既保证网络传输与持久化层的纯 JSON 兼容性,又能在反序列化时 100% 还原原始对象。本文将以本仓库 README 为骨架,结合 src/index.ts、src/transformer.ts、src/plainer.ts 等源码,系统讲解它的安装、四个核心 API、Next.js 集成、类型注册机制与底层实现原理。读完本文,你将能在任何服务端渲染、RPC 或跨进程场景中放心地序列化复杂的 JavaScript 数据类型,并为第三方库类型编写自定义转换器。
背景:为什么 JSON 序列化不够用
JSON 是事实上的数据交换标准,但JSON.stringify对很多 JavaScript 运行时数据类型并不友好。正如 README 的 Backstory 部分所述,Blitz 团队在日常开发中经常与Date、Map、Set、BigInt打交道,而直接使用JSON.stringify时这些类型要么被静默转换(Date变成字符串、Map变成空对象{}),要么直接抛错(BigInt会抛出TypeError),开发者只能手动做繁琐的转换与还原。
Superjson 的定位是「在JSON.stringify和JSON.parse之上的一层薄封装(a thin wrapper)」,它让序列化输出依然是一份标准的 JSON 对象(可以被任何语言、任何客户端解析),同时把类型信息记录在附加的meta字段中,实现无损往返。README 中列举的核心特性包括:
- 可靠的序列化与反序列化(Reliable serialization and deserialization);
- 类型安全与自动补全(Type safety with autocompletion),得益于完整的 TypeScript 类型定义(见 src/types.ts 中的
SuperJSONValue、SuperJSONResult); - 可忽略的运行时开销(Negligible runtime footprint);
- 框架无关(Framework agnostic);
- 完美解决 Next.js 的限制:
getServerSideProps和getInitialProps等数据钩子无法传递Date等对象,Superjson 正是为此而生。
快速开始:安装
README 推荐使用包管理器安装,例如:
yarn add superjson由于本仓库的 package.json 采用 ESM 输出("type": "module",main指向dist/index.js)并要求 Node.js >= 16,你也可以用等价方式安装:
npm install superjson # 或 pnpm add superjson安装完成后即可在项目中导入。仓库导出默认实例的四个核心函数(stringify、parse、serialize、deserialize),也导出SuperJSON类本身及若干注册方法(见 src/index.ts)。
基础用法:stringify 与 parse
README 强调:「如果你会用JSON.stringify,你就已经会使用 Superjson!」基础用法只需要两个函数。
序列化任意 JavaScript 表达式:
import superjson from 'superjson'; const jsonString = superjson.stringify({ date: new Date(0) }); // jsonString === '{"json":{"date":"1970-01-01T00:00:00.000Z"},"meta":{"values":{date:"Date"}}}'反序列化回原始对象:
const object = superjson.parse< { date: Date } >(jsonString); // object === { date: new Date(0) }从源码看,stringify只是JSON.stringify(this.serialize(object)),parse则是this.deserialize(JSON.parse(string), { inPlace: true })(见 src/index.ts)。注意parse默认走inPlace: true的原地还原路径,在解析后立即使用结果对象的场景下性能更好。
易用背后的事实:输出是一份嵌套结构——json字段保存转换后完全 JSON 兼容的数据,meta字段保存类型注解。这就是下面要讲的高级 API 暴露出来的结构。
深入输出结构:serialize 与 deserialize
当需要低层访问输出中的json与meta数据时,README 建议使用serialize和deserialize这对函数。典型场景是:你的 API 希望对所有客户端保持 JSON 兼容,同时又希望把元数据一并传输,让使用 Superjson 的客户端能够完整反序列化。
const object = { normal: 'string', timestamp: new Date(), test: /superjson/, }; const { json, meta } = superjson.serialize(object); /* json = { normal: 'string', timestamp: "2020-06-20T04:56:50.293Z", test: "/superjson/", }; // 注意:normal 没有出现在 meta 中,meta 只记录"特殊类型"的路径 meta = { values: { timestamp: ['Date'], test: ['regexp'], } }; */几个值得注意的细节:
meta.values是最小化树(Minimised Tree)结构:叶子节点是一个数组(如['Date']),内部节点则用对象表达路径。路径中的点号(.)与反斜杠会被转义,相关解析逻辑在 src/pathstringifier.ts 中实现,测试用例见 src/index.test.ts 的 "repro #310: meta path escape bug"。- 序列化结果还会携带格式版本号
meta.v = 1(见 src/index.ts),反序列化时会根据版本决定是否启用兼容旧版路径的解析模式(见 src/plainer.ts 的enableLegacyPaths)。 - 当同一对象被多处引用时,
meta还会包含referentialEqualities字段,用于还原引用相等性(详见下文"引用相等性与去重"小节)。
支持的数据类型总览
README 给出了一张完整的能力对照表:
| 类型 | 标准 JSON 是否支持? | Superjson 是否支持? |
|---|---|---|
string | ✅ | ✅ |
number | ✅ | ✅ |
boolean | ✅ | ✅ |
null | ✅ | ✅ |
Array | ✅ | ✅ |
Object | ✅ | ✅ |
undefined | ❌ | ✅ |
bigint | ❌ | ✅ |
Date | ❌ | ✅ |
RegExp | ❌ | ✅ |
Set | ❌ | ✅ |
Map | ❌ | ✅ |
Error | ❌ | ✅ |
URL | ❌ | ✅ |
基于 src/transformer.ts 中的simpleRules与compositeRules,该表还可以进一步扩充(均由本仓库源码与测试证实,见 src/index.test.ts):
- 特殊数字:
NaN、Infinity、-Infinity、-0会被编码为对应字符串标记('NaN'、'Infinity'、'-Infinity'、'-0')并附带'number'注解还原(见 src/transformer.ts); - Typed Arrays:
Int8Array、Uint8Array、Float64Array等 9 种标准 TypedArray 及BigInt64Array/BigUint64Array均受支持,注解形如['typed-array', 'Int8Array'],BigInt 元素会先转为字符串再还原(见 src/transformer.ts); - Symbol:经
registerSymbol注册后的 Symbol 可被序列化(见下文); - 注册过的类实例:经
registerClass注册的类可完整还原其原型链与属性(见下文)。
在 Next.js 中使用
Next.js 的getServerSideProps、getInitialProps和getStaticProps数据钩子不允许传输Date之类的 JavaScript 对象——直接返回会报错,除非你手动把Date转成字符串等。Superjson 正是绕过该限制的完美工具。README 提供了两种接入方式。
方式一:SWC 插件(实验性,Next.js v13 及以上)
SWC 插件目前仍处于实验阶段,但承诺带来显著的编译速度提升。首先安装官方配套插件:
yarn add next-superjson-plugin然后在next.config.js中启用:
// next.config.js module.exports = { experimental: { swcPlugins: [ [ 'next-superjson-plugin', { excluded: [], }, ], ], }, };excluded数组用于排除不希望被自动转换的路径,默认留空数组表示不排除任何属性。
方式二:稳定的 Babel 转换
如果项目使用 Babel,可以改用官方 Babel 插件:
yarn add babel-plugin-superjson-next然后在项目根目录的.babelrc中加入插件(若没有该文件则新建):
{ "presets": ["next/babel"], "plugins": [ ... "superjson-next" // 👈 ] }完成以上任意一种配置后,就可以在getServerSideProps等数据钩子中安全使用所有 JavaScript 数据类型了。需要说明的是,SWC 插件与 Babel 插件分别对应 Next.js 两种编译管线,二者选其一即可;README 中 SWC 方式标注为 experimental(v13 及以上),Babel 方式为稳定方案。
完整 API 参考
serialize
将任意 JavaScript 值序列化为 JSON 兼容对象。
const object = { normal: 'string', timestamp: new Date(), test: /superjson/, }; const { json, meta } = serialize(object);返回:json与meta,二者均为 JSON 兼容值(对应 src/types.ts 中的SuperJSONResult类型)。
底层实现由 src/plainer.ts 的walker深度优先遍历完成:对每个值依次尝试复合规则(类、Symbol、自定义转换器、TypedArray)与简单规则(undefined、bigint、Date、Error、RegExp、Set、Map、特殊数字、URL),命中规则的叶子节点会记录类型注解;遍历期间还会维护一个seenObjects缓存以避免重复处理,并在dedupe开启时对重复对象输出null(见下文)。
deserialize
将 Superjson 的输出还原为原始值。
const { json, meta } = serialize(object); deserialize({ json, meta }, { inPlace: true });参数选项:
inPlace: boolean- 默认值:
false - 含义:是否原地修改输入的
json对象,而不是返回一份深拷贝 - 说明:在可以安全修改输入对象的前提下,
inPlace: true对大型对象会显著提升性能
- 默认值:
从源码看,当inPlace为false时,deserialize先用copy-anything库的copy(json)做深拷贝,再依次应用meta.values的类型注解(applyValueAnnotations)与meta.referentialEqualities的引用相等性注解(applyReferentialEqualityAnnotations),见 src/index.ts 与 src/plainer.ts。
返回:你的原始值。
stringify
序列化并字符串化你的 JavaScript 值。
const object = { normal: 'string', timestamp: new Date(), test: /superjson/, }; const jsonString = stringify(object);返回:string。
parse
解析stringify返回的 JSON 字符串并反序列化。
const jsonString = stringify(object); parse(jsonString);返回:你的原始值。parse内部会以inPlace: true模式调用deserialize(见 src/index.ts),因此如果你是从字符串直接还原对象,这一对函数是最简搭配。
高级特性:注册机制与自定义转换器
Superjson 出于保持包体积最小的考虑,默认只支持内置数据类型(README 的 Recipes 章节明确说明了这一点)。对于非内置类型,你需要通过注册 API 显式声明,并确保注册代码在任何 Superjson 调用之前执行——在 Next.js 项目中,_app.ts是放置这些注册代码的好位置。
registerClass:注册自定义类
SuperJSON.registerClass(User, { allowProps: ['username'] });registerClass接受一个类,可选的第二参数支持两种形态:字符串(指定 identifier)或对象{ identifier?: string; allowProps?: string[] }(见 src/class-registry.ts):
identifier:类的唯一标识,默认使用构造函数名c.name;allowProps:只序列化指定的属性白名单。测试用例 "works with custom allowedProps"(src/index.test.ts)验证了password不会被序列化、username会被保留的行为。
反序列化时,注册过的类会通过Object.assign(Object.create(clazz.prototype), v)重建实例(见 src/transformer.ts),因此类实例的原型方法(如测试中的pet.woof())在往返后依然可用。同一对象被多处引用或嵌套类(如User持有Pet)的场景也有对应测试覆盖(见 src/index.test.ts)。
registerSymbol:注册 Symbol
const cool = Symbol('cool'); SuperJSON.registerSymbol(cool);Symbol 的注册与查找基于description或显式 identifier,底层使用 src/double-indexed-kv.ts 的双向索引(键 ↔ 值双向可查),实现位于 src/registry.ts 与 src/index.ts。未注册的 Symbol 不会被序列化;反序列化时若遇到未知标识符会抛出Trying to deserialize unknown symbol错误(见 src/transformer.ts)。
registerCustom:自定义转换器(Recipes)
README 以Decimal.js/Prisma.Decimal为例给出完整配方:
import { Decimal } from 'decimal.js'; SuperJSON.registerCustom<Decimal, string>( { isApplicable: (v): v is Decimal => Decimal.isDecimal(v), serialize: v => v.toJSON(), deserialize: v => new Decimal(v), }, 'decimal.js' );自定义转换器由四个部分组成(见 src/custom-transformer-registry.ts):
isApplicable(v):类型守卫,判断某个值是否属于该转换器;serialize(v):把值转换为 JSON 兼容的中间形态;deserialize(v):把中间形态还原为原类型;name(即registerCustom的第二个参数):转换器的唯一标识,会写入注解(形如['custom', 'decimal.js'])。
序列化时,Superjson 按注册顺序查找第一个isApplicable返回 true 的转换器;反序列化时按name精确匹配(见 src/transformer.ts)。除了decimal.js,仓库测试中还演示了基于mongodb的ObjectID转换器(见 src/index.test.ts)。注意同一标识符的转换器注册后不可重复覆盖(src/custom-transformer-registry.ts),应在应用启动早期统一注册。
allowErrorProps:扩展 Error 序列化字段
Error默认只序列化name、message与(若存在)cause字段。若你的自定义 Error 还带有code、meta等附加属性,需要显式放行:
SuperJSON.allowErrorProps('code'); SuperJSON.allowErrorProps('meta');对应的实现见 src/transformer.ts,测试用例 "allowErrorProps(...) (#91)"(src/index.test.ts)验证了附加属性能够被完整还原。
底层原理速览:walker、注解与安全性
如果上述 API 满足不了你的好奇心,本仓库源码中还藏着几个值得了解的实现细节:
- 双阶段输出:
serialize一次遍历同时产出json与meta注解(values最小化树),而非二次扫描,因此运行时开销极小(核心逻辑在 src/plainer.ts)。 - 引用相等性(referential equalities):同一对象在数据中出现多次时,默认模式会把最短路径作为代表路径,其余路径记录进
meta.referentialEqualities,反序列化时统一指向同一实例(见 src/plainer.ts 与测试 src/index.test.ts)。 - dedupe 去重模式:
new SuperJSON({ dedupe: true })开启后,重复出现的对象只序列化一次,其余位置替换为null,适合追求极致体积的大对象场景(见 src/index.ts 与测试 src/index.test.ts)。 - 循环引用防护:遍历路径上重复出现的对象会被替换为
null,避免无限递归(见 src/plainer.ts)。 - 原型污染防护:属性名若为
__proto__、constructor、prototype会直接抛出异常,从源头杜绝原型污染风险(见 src/plainer.ts 与 src/accessDeep.ts)。 - 实例隔离:
new SuperJSON()创建的实例拥有独立的注册表,不同实例之间互不影响(测试 "superjson instances are independent of one another" 见 src/index.test.ts);默认导出的函数则绑定到模块级单例(src/index.ts)。
总结
从"一行代码搞定Date序列化"到"为任意第三方库编写自定义转换器",Superjson 用一份 JSON 兼容的json+ 类型注解meta结构,把 JavaScript 类型系统的表达能力完整地带过了 JSON 边界。若想进一步深入,建议阅读本仓库的 src/plainer.ts(遍历与注解生成)、src/transformer.ts(全部内置转换规则)以及 src/index.test.ts(覆盖各类型的回归测试用例),它们既是实现证据,也是绝佳的集成参考。
- 开发工具
【免费下载链接】superjson
Safely serialize JavaScript expressions to a superset of JSON, which includes Dates, BigInts, and more.
相关推荐
ComfyUI-VideoHelperSuite零基础入门:从核心功能到实战应用
ComfyUI VideoHelperSuite零基础入门:从核心功能到实战应用 一、核心功能解析:视频工作流的全能助手 ComfyUI VideoHelper
开发工具Wasp 的 superjson 序列化机制:让 Query 与 Action 传输 Date、BigInt、Map 和 Prisma.Decimal
Wasp 的 superjson 序列化机制:让 Query 与 Action 传输 Date、BigInt、Map 和 Prisma.Decimal Wasp
后端前端Web框架编译器开发工具代码生成SuperJSON终极指南:如何安全序列化JavaScript复杂数据类型
SuperJSON终极指南:如何安全序列化JavaScript复杂数据类型 SuperJSON是一个强大的JavaScript库,它能够安全地序列化JavaSc
开源治理CLI认证鉴权
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考