news 2026/10/15 1:55:26

Superjson 序列化指南:让 Date、BigInt、Map 等类型安全穿越 JSON 边界

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Superjson 序列化指南:让 Date、BigInt、Map 等类型安全穿越 JSON 边界
  • 开发工具

【免费下载链接】superjson

Safely serialize JavaScript expressions to a superset of JSON, which includes Dates, BigInts, and more.

项目地址:https://gitcode.com/gh_mirrors/su/superjson
点击查看免费下载

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.

项目地址:https://gitcode.com/gh_mirrors/su/superjson
点击查看免费下载
上一篇:Cogent Core 开源项目教程
下一篇:【亲测免费】 Wino-Mail 开源项目教程

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 1:55:19

从零实现前端性能监控SDK:性能、行为与错误上报解析

这个月&#xff0c;我把下班后的碎片时间全砸进了一个代号叫Apollo-11的项目里。它不是登月计划&#xff0c;而是一个属于自己的前端性能监控 SDK。为什么起这么唬人的名字&#xff1f;因为我的目标很直白&#xff1a;像登月一样&#xff0c;从零把一块石头送上天。具体来说&am…

作者头像 李华
网站建设 2026/10/15 1:54:13

Vue3 HelloWorld实战:从零搭建你的第一个组件

简介&#xff1a;这是一份面向前端初学者及旧版框架用户的Vue3入门示例项目&#xff0c;通过最简代码演示新一代框架的组合式写法、组件状态管理、生命周期钩子等核心概念&#xff0c;并与传统的选项式写法进行对照&#xff0c;帮助读者快速建立现代前端开发思维。资源包体积很…

作者头像 李华
网站建设 2026/10/15 1:54:05

让网页拥有棉浆纸的触感:水彩质感 UI 核心技法全景复盘

十月十四日的午后&#xff0c;阳光斜照在书桌一角那盒温莎牛顿水彩颜料上。金属颜料盒被擦拭得锃亮&#xff0c;几支常用的松尾水彩画笔整齐地插在洗笔筒里。 在很多人的印象中&#xff0c;网页前端界面天然是由无机质的冰冷像素构成的&#xff1a;绝对笔直的水平线、精确到像素…

作者头像 李华
网站建设 2026/10/15 1:52:25

REFramework 上手指南:10 分钟跑通你的第一个 RE 引擎 Mod

REFramework 上手指南&#xff1a;10 分钟跑通你的第一个 RE 引擎 Mod 【免费下载链接】REFramework Mod loader, scripting platform, and VR support for all RE Engine games 项目地址: https://gitcode.com/GitHub_Trending/re/REFramework REFramework 是覆盖 RE 引…

作者头像 李华