一、问题背景与原生局限
JavaScript的JSON.stringify()和JSON.parse()在处理特殊数据结构时存在明显局限,特别是面对类型化数组(TypedArray)时。直接序列化会得到无意义的空对象:
const arr = new Uint8Array([1,2,3]);
console.log(JSON.stringify(arr)); // {"0":1,"1":2,"2":3}
这种结果不仅丢失了数组类型信息,也无法正确反序列化还原。这在处理二进制数据、WebGL缓冲区等场景时会造成严重问题。
二、核心解决方案设计
要实现完整的类型化数组序列化,需要三个关键步骤:
- 类型标记:为每个TypedArray添加类型标识
- 数据编码:将二进制数据转换为Base64字符串
- 自定义解析:通过reviver函数重建原始对象
三、核心代码实现解析
3.1 类型化数组转Base64
_typedArrayToBase64(typedArray) {
const bytes = new Uint8Array(typedArray.buffer);
let binary = '';
for (let i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
}
通过遍历字节数据构建二进制字符串,最终使用btoa转换为Base64编码。
3.2 Base64转类型化数组
_base64ToTypedArray(base64String, TypedArrayConstructor) {
const binaryString = atob(base64String);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < bytes.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return new TypedArrayConstructor(bytes.buffer);
}
逆向操作使用atob解码,重建字节缓冲区后创建对应类型的数组。
3.3 原型方法扩展
typedArrayConstructors.forEach(TypedArray => {
if (!TypedArray.prototype.toJSON) {
TypedArray.prototype.toJSON = function() {
return {
__type: TypedArray.name,
data: this._typedArrayToBase64(this)
};
}
}
});
为所有类型化数组添加toJSON方法,在序列化时自动触发转换逻辑。
四、完整实战案例演示
const TypedArrayJSON = {
// 辅助函数:类型化数组到 Base64
_typedArrayToBase64(typedArray) {
const buffer = typedArray.buffer;
const bytes = new Uint8Array(buffer);
let binary = '';
for (let i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
},
// 辅助函数:Base64 到类型化数组
_base64ToTypedArray(base64String, TypedArrayConstructor) {
const binaryString = atob(base64String);
const len = binaryString.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return new TypedArrayConstructor(bytes.buffer);
},
// 初始化:为 Typed Array 添加 toJSON 方法
_init() {
const typedArrayConstructors = [Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array, BigInt64Array, BigUint64Array, Float32Array, Float64Array];
typedArrayConstructors.forEach(TypedArray => {
if (!TypedArray.prototype.toJSON) {
TypedArray.prototype.toJSON = function() {
return {
__type: TypedArray.name,
data: TypedArrayJSON._typedArrayToBase64(this)// 使用 this 访问对象内部方法
};
}
;
}
}
);
},
// 自定义 replacer 函数 (stringify 使用)
_typedArrayReplacer(key, value) {
if (value && typeof value === 'object' && value.__type && value.data) {
return value;
}
return value;
},
// 自定义 reviver 函数 (parse 使用)
_typedArrayReviver(key, value) {
if (value && typeof value === 'object' && value.__type && value.data) {
switch (value.__type) {
case 'Int8Array':
return TypedArrayJSON._base64ToTypedArray(value.data, Int8Array);
case 'Uint8Array':
return TypedArrayJSON._base64ToTypedArray(value.data, Uint8Array);
case 'Uint8ClampedArray':
return TypedArrayJSON._base64ToTypedArray(value.data, Uint8ClampedArray);
case 'Int16Array':
return TypedArrayJSON._base64ToTypedArray(value.data, Int16Array);
case 'Uint16Array':
return TypedArrayJSON._base64ToTypedArray(value.data, Uint16Array);
case 'Int32Array':
return TypedArrayJSON._base64ToTypedArray(value.data, Int32Array);
case 'Uint32Array':
return TypedArrayJSON._base64ToTypedArray(value.data, Uint32Array);
case 'BigInt64Array':
return TypedArrayJSON._base64ToTypedArray(value.data, BigInt64Array);
case 'BigUint64Array':
return TypedArrayJSON._base64ToTypedArray(value.data, BigUint64Array);
case 'Float32Array':
return TypedArrayJSON._base64ToTypedArray(value.data, Float32Array);
case 'Float64Array':
return TypedArrayJSON._base64ToTypedArray(value.data, Float64Array);
default:
return value;
}
}
return value;
},
// 增强的 JSON.stringify
stringify(obj) {
return JSON.stringify(obj, this._typedArrayReplacer);
// 使用 this 访问对象内部方法
},
// 增强的 JSON.parse
parse(str) {
return JSON.parse(str, this._typedArrayReviver);
// 使用 this 访问对象内部方法
}
};
// 初始化 TypedArrayJSON 对象(添加 toJSON 方法)
TypedArrayJSON._init();
// 创建测试数据
const complexData = {
buffer: new Uint8Array([72, 101, 108, 108, 111]), // "Hello"的ASCII
floatArray: new Float32Array([1.1, 2.2, 3.3]),
timestamp: Date.now()
};
// 序列化
const jsonStr = TypedArrayJSON.stringify(complexData);
console.log(jsonStr);
// 输出结果:
// {
// "buffer":{"__type":"Uint8Array","data":"SGVsbG8="},
// "floatArray":{"__type":"Float32Array","data":"AACAPwAAAEAAAMCC"},
// "timestamp":1717986912345
// }
// 反序列化
const parsedData = TypedArrayJSON.parse(jsonStr);
console.log(parsedData.floatArray instanceof Float32Array); // true
console.log(new TextDecoder().decode(parsedData.buffer)); // "Hello"
五、方案优势与注意事项
核心优势:
- 完整保留类型信息
- 数据体积优化(Base64相比JSON数组更紧凑)
- 支持所有标准类型化数组
- 完美兼容现有JSON工作流
使用注意事项:
- 需在代码初始化时调用
_init()方法 - BigInt类型需要额外处理
- 注意数据安全(Base64非加密编码)
- 服务端需要对应解析逻辑
六、扩展应用场景
本方案可应用于:
- WebSocket二进制数据传输
- IndexedDB存储二进制数据
- Web Worker间大数据传输
- Canvas/WebGL图像数据处理
- 科学计算数据持久化
通过自定义JSON序列化方案,我们成功突破了JavaScript原生能力的限制,为复杂数据结构的处理提供了标准化解决方案。这种模式也可扩展到其他特殊数据类型(如Date、RegExp等),为现代Web应用开发提供强有力的支撑。