JavaScript应用中更丰富的数据序列化方案及特殊类型处理咨询
嘿,这个问题我太熟了——在Node.js+TS/JS的Web项目里,JSON搞不定Set、UUID、Date这些特殊类型确实是个高频痛点,完全不用自己手动硬写一堆转换逻辑,有不少成熟的方案可以直接用,我给你分情况捋清楚:
内置方案:用JSON自带的replacer/reviver函数
这是最基础的零依赖方案,利用JSON.stringify的第二个参数(replacer)和JSON.parse的第二个参数(reviver),自己定义特殊类型的序列化/反序列化规则,足够应付简单场景。
举个覆盖多种类型的例子:
// 序列化:把特殊类型转成JSON能识别的结构 const data = { userId: '123e4567-e89b-12d3-a456-426614174000', // UUID字符串本身没问题,要是UUID对象也能处理 tags: new Set(['frontend', 'nodejs']), createdAt: new Date(), avatar: Buffer.from('some-image-data') }; const jsonStr = JSON.stringify(data, (key, value) => { if (value instanceof Set) { return { $type: 'Set', value: Array.from(value) }; // 标记类型+存数组 } if (value instanceof Date) { return { $type: 'Date', value: value.toISOString() }; // 存标准ISO字符串 } if (value instanceof Buffer) { return { $type: 'Buffer', value: value.toString('base64') }; // 字节数组转base64 } // 要是用uuid库生成的UUID对象,也可以统一转字符串 if (typeof value === 'object' && value?.uuid) { return { $type: 'UUID', value: value.uuid }; } return value; }); // 反序列化:把标记的结构还原成原类型 const parsedData = JSON.parse(jsonStr, (key, value) => { if (value?.$type === 'Set') { return new Set(value.value); } if (value?.$type === 'Date') { return new Date(value.value); } if (value?.$type === 'Buffer') { return Buffer.from(value.value, 'base64'); } if (value?.$type === 'UUID') { // 要是需要转回UUID对象,用uuid库的parse方法就行,否则直接返回字符串也可 return value.value; } return value; });
第三方库:开箱即用的省心方案
如果不想自己写规则,直接用成熟库就行,尤其是TypeScript项目,这些库大多自带类型支持:
- superjson:TS友好度拉满,自动处理Set、Map、Date、UUID、Symbol、BigInt这些常见特殊类型,甚至能保留TS的类型信息。用法超简单:
import superjson from 'superjson'; const data = { tags: new Set(['react', 'typescript']), lastLogin: new Date(), userId: 'a1b2c3d4-5678-90ef-ghij-klmnopqrstuv' }; // 序列化 const jsonStr = superjson.stringify(data); // 反序列化,自动推断原类型 const parsedData = superjson.parse<typeof data>(jsonStr);
它会在序列化时给特殊类型加隐形标记,反序列化时自动还原,完全不用你操心转换逻辑。
- flatted:如果你的数据有循环引用(JSON默认完全不支持)+ 特殊类型,这个库是绝佳选择,它是JSON的扩展,能序列化循环引用和大部分JS内置类型,用法和原生JSON几乎一致:
import flatted from 'flatted'; // 带循环引用的对象 const obj = { name: 'test', self: null }; obj.self = obj; obj.tags = new Set(['loop', 'special']); const str = flatted.stringify(obj); const parsed = flatted.parse(str); // 解析后parsed.self === parsed,Set也能正常还原
- 针对UUID的专属处理:如果只是单独处理UUID,用
uuid库的话,其实可以直接把UUID对象转成字符串存储(JSON本身支持字符串),反序列化时如果需要再转回UUID对象:
import { v4 as uuidv4, parse as uuidParse } from 'uuid'; // 序列化时转字符串 const user = { id: uuidv4().toString() }; // 反序列化时转回UUID对象(如果业务需要) const userId = uuidParse(user.id);
TypeScript专属优化:类型安全保障
用TS的话,最好给特殊类型的序列化结构加类型定义,或者写类型守卫确保类型安全:
// 定义序列化后的Set结构类型 type SerializableSet<T> = { $type: 'Set'; value: T[] }; // 类型守卫:判断是否是序列化后的Set function isSerializableSet<T>(value: unknown): value is SerializableSet<T> { return typeof value === 'object' && value !== null && '$type' in value && value.$type === 'Set' && Array.isArray((value as any).value); } // 反序列化时用类型守卫确保类型正确 const parsedValue = JSON.parse(jsonStr, (key, val) => { if (isSerializableSet(val)) { return new Set(val.value) as Set<T>; } return val; });
兜底方案:高度自定义转换
如果遇到极其特殊的类型(比如自定义类、复杂字节结构),可以针对性写转换逻辑,比如自定义类:
class User { constructor(public id: string, public name: string) {} // 自定义序列化方法 toJSON() { return { $type: 'User', id: this.id, name: this.name }; } // 自定义反序列化方法 static fromJSON(obj: any) { return new User(obj.id, obj.name); } } // 序列化 const user = new User('1', 'Alice'); const jsonStr = JSON.stringify(user); // 反序列化 const parsedUser = User.fromJSON(JSON.parse(jsonStr));
总结一下:简单场景用原生的replacer/reviver,TS项目优先选superjson,有循环引用用flatted,特殊类型按需写自定义逻辑就行,完全不用全手动撸所有转换~
内容的提问来源于stack exchange,提问作者Rob N
相关产品推荐
相关产品推荐

