
在浏览器环境中处理超大json对象并将其转换为blob时,传统的`json.stringify()`方法可能因字符串长度限制(如chrome的500mb)而失败。本文介绍一种创新的解决方案,通过修改json序列化逻辑,使其在生成json文本时直接以分块(`blob`或字符串)的形式输出,而非一次性生成完整字符串,从而有效规避内存限制,实现将任意嵌套的内存中pojo安全地转换为blob。
在Web应用程序中,我们经常需要将JavaScript对象(Plain Old JavaScript Object, POJO)序列化为JSON字符串,并进一步封装成Blob对象,以便进行文件下载、上传或存储。通常的做法是使用JSON.stringify()将对象转换为字符串,然后将该字符串传递给Blob构造函数:
let myLargeJson = { /* ... 包含大量数据 ... */ };
let blob = new Blob([JSON.stringify(myLargeJson)], {type: "application/json"});然而,当myLargeJson对象非常庞大时,JSON.stringify()生成的字符串可能会超出浏览器对单个JavaScript字符串的最大长度限制(例如,Chrome中约为500MB)。一旦超出此限制,程序就会抛出“Maximum string length exceeded”错误,导致转换失败。
本教程旨在提供一种在浏览器端解决此问题的方法,即不依赖于生成完整的中间字符串,而是直接将内存中的POJO转换为Blob对象。
Blob构造函数的一个强大特性是它接受一个blobParts数组作为输入。这个数组的元素可以是DOMString(字符串)、ArrayBuffer、ArrayBufferView或甚至其他的Blob对象。这意味着我们可以将最终的JSON内容分解成多个小块(字符串或嵌套的Blob),然后将这些小块组合起来形成一个大的Blob,而无需在任何时候在内存中持有完整的JSON字符串。
例如,一个2.7GB的Blob可以通过将多个500MB的字符串块组合起来创建:
const header = 24; // 示例值,实际可能不同
const bytes = new Uint8Array((512 * 1024 * 1024) - header); // 约500MB
const bigStr = new TextDecoder().decode(bytes); // 生成一个大字符串
const arr = [];
for (let i = 0; i < 5; i++) { // 拼接5个大字符串
arr.push(bigStr);
}
console.log(new Blob(arr).size); // 输出约2.5GB (5 * 512MB - header * 5)这个原理启发我们,可以通过修改JSON序列化逻辑,使其在遇到对象或数组时,不将所有子元素的字符串表示拼接成一个大字符串,而是将它们及其分隔符作为独立的BlobParts,然后用这些BlobParts创建一个新的Blob。最终,整个JSON结构将由一个嵌套的Blob链表示。
为了实现分块构建Blob,我们需要一个自定义的JSON序列化器。这里我们基于一个经典的json2.js实现进行改造,将其stringify方法替换为一个新的blobify方法。
核心改造点在于str函数,它负责递归地处理JSON对象的各个部分。原版str函数返回的是字符串,现在我们需要它返回Blob对象(对于复杂类型如对象和数组)或字符串(对于原始类型如数字、布尔值、null和较短的字符串)。
以下是改造后的JSON.blobify及其辅助函数的关键部分:
// ... (json2.js 原始代码的导入和初始化部分) ...
// 自定义join函数,用于连接BlobPart数组
const join = (arr, joint) => {
// 将数组元素与连接符交错排列,并扁平化
// 例如:[A, B, C] 和 ',' => [A, ',', B, ',', C]
return arr.map((v) => [v, joint]).flat().slice(0, -1);
};
function quote(string) {
// 字符串引用和转义逻辑保持不变,返回字符串
// ...
}
function str(key, holder) {
let i, k, v, length, mind = gap, partial, value = holder[key];
// ... (toJSON 和 replacer 逻辑保持不变) ...
switch (typeof value) {
case 'string':
return quote(value); // 短字符串直接返回
case 'number':
return isFinite(value) ? String(value) : 'null';
case 'boolean':
case 'null':
return String(value);
case 'object':
if (!value) {
return 'null';
}
gap += indent;
partial = [];
if (Object.prototype.toString.apply(value) === '[object Array]') {
// 处理数组
length = value.length;
for (i = 0; i < length; i += 1) {
partial[i] = str(i, value) || 'null'; // 递归调用str,获取子元素的BlobPart
}
v = partial.length === 0
? new Blob(['[]']) // 空数组直接创建Blob
: gap
? new Blob(['[\n', gap, ...join(partial, ',\n' + gap), '\n', mind, ']']) // 格式化数组
: new Blob(['[', ...join(partial, ','), ']']); // 紧凑数组
gap = mind;
return v; // 返回一个Blob对象
}
// 处理普通对象
// ... (根据replacer或遍历所有key的逻辑) ...
if (rep && typeof rep === 'object') {
length = rep.length;
for (i = 0; i < length; i += 1) {
if (typeof rep[i] === 'string') {
k = rep[i];
v = str(k, value);
if (v) {
partial.push(new Blob([quote(k) + (gap ? ': ' : ':'), v]));
}
}
}
} else {
for (k in value) {
if (Object.prototype.hasOwnProperty.call(value, k)) {
v = str(k, value);
if (v) {
partial.push(new Blob([quote(k), (gap ? ': ' : ':'), v]));
}
}
}
}
v = partial.length === 0
? new Blob(['{}']) // 空对象直接创建Blob
: gap
? new Blob(['{\n', gap, ...join(partial, ',\n' + gap), '\n', mind, '}']) // 格式化对象
: new Blob(['{', ...join(partial, ','), '}']); // 紧凑对象
gap = mind;
return v; // 返回一个Blob对象
}
}
// 新增的JSON.blobify方法作为入口
if (true) { // 原始json2.js中用于判断是否已有JSON.stringify的逻辑,这里简化为true
meta = { /* ... 字符转义映射 ... */ };
JSON.blobify = function (value, replacer, space) {
var i;
gap = '';
indent = '';
if (typeof space === 'number') {
for (i = 0; i < space; i += 1) {
indent += ' ';
}
} else if (typeof space === 'string') {
indent = space;
}
rep = replacer;
if (replacer && typeof replacer !== 'function' &&
(typeof replacer !== 'object' ||
typeof replacer.length !== 'number')) {
throw new Error('JSON.stringify'); // 保持与JSON.stringify相同的错误处理
}
// 调用str函数,从根对象开始构建Blob
return str('', {'': value});
};
}一旦JSON.blobify函数被添加到全局JSON对象上,你就可以像使用JSON.stringify一样使用它:
(async () => {
const largeData = {
metadata: {
timestamp: new Date(),
version: "1.0"
},
items: Array(1000000).fill(0).map((_, i) => ({
id: i,
name: `Item ${i}`,
description: `This is a long description for item ${i}.`.repeat(10),
details: {
category: "electronics",
price: Math.random() * 1000,
available: true
}
}))
};
// 使用改造后的JSON.blobify将对象直接转换为Blob
const asBlob = JSON.blobify(largeData, null, 2); // 带有2个空格缩进
console.log({asBlob}); // 输出Blob对象信息
// 为了验证,我们可以将Blob转换回字符串并解析
// 注意:如果原始JSON非常大,asBlob.text() 仍然可能创建大字符串,
// 但这发生在Blob生成之后,且取决于后续操作,而非Blob创建本身。
try {
const asString = await asBlob.text();
console.log("Blob内容大小:", asString.length / (1024 * 1024), "MB");
const parsedData = JSON.parse(asString);
console.log("解析成功,数据结构与原始对象一致:", parsedData.items[0]);
} catch (error) {
console.error("从Blob转换为字符串或解析时发生错误:", error);
}
})();通过这种分块构建Blob的策略,我们成功规避了浏览器对JavaScript字符串长度的限制,使得在浏览器端处理和存储超大型JSON数据成为可能。这为Web应用程序在处理大数据集时提供了更大的灵活性和鲁棒性。
以上就是将大型JSON对象高效转换为Blob以规避字符串长度限制的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号