新闻中心

将大型JSON对象高效转换为Blob以规避字符串长度限制

2025-11-01
浏览次数:
返回列表

将大型JSON对象高效转换为Blob以规避字符串长度限制

在浏览器环境中处理超大json对象并将其转换为blob时,传统的`json.stringify()`方法可能因字符串长度限制(如chrome的500mb)而失败。本文介绍一种创新的解决方案,通过修改json序列化逻辑,使其在生成json文本时直接以分块(`blob`或字符串)的形式输出,而非一次性生成完整字符串,从而有效规避内存限制,实现将任意嵌套的内存中pojo安全地转换为blob。

引言:大型JSON对象与Blob转换的挑战

在Web应用程序中,我们经常需要将J*aScript对象(Plain Old J*aScript Object, POJO)序列化为JSON字符串,并进一步封装成Blob对象,以便进行文件下载、上传或存储。通常的做法是使用JSON.stringify()将对象转换为字符串,然后将该字符串传递给Blob构造函数:

let myLargeJson = { /* ... 包含大量数据 ... */ };
let blob = new Blob([JSON.stringify(myLargeJson)], {type: "application/json"});

然而,当myLargeJson对象非常庞大时,JSON.stringify()生成的字符串可能会超出浏览器对单个J*aScript字符串的最大长度限制(例如,Chrome中约为500MB)。一旦超出此限制,程序就会抛出“Maximum string length exceeded”错误,导致转换失败。

本教程旨在提供一种在浏览器端解决此问题的方法,即不依赖于生成完整的中间字符串,而是直接将内存中的POJO转换为Blob对象。

解决方案核心:分块构建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链表示。

Mureka Mureka

Mureka是昆仑万维最新推出的一款AI音乐创作工具,输入歌词即可生成完整专属歌曲。

Mureka 1091 查看详情 Mureka

实现细节:改造JSON序列化器

为了实现分块构建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});
    };
}

代码解释

  1. join(arr, joint) 函数:这是一个辅助函数,用于将一个BlobPart数组arr与一个分隔符joint交错连接。它返回一个新的BlobPart数组,而不是一个单一的字符串。例如,join([blobA, blobB], ',')会返回[blobA, ',', blobB]。这是避免生成大字符串的关键。
  2. str(key, holder) 函数的修改
    • 对于原始类型(字符串、数字、布尔值、null),它仍然返回其字符串表示,因为这些通常不会导致长度限制问题。
    • 对于数组和对象,它不再将所有子元素的字符串表示拼接起来。相反,它递归调用str来获取子元素的BlobPart(可能是一个字符串或另一个Blob)。
    • 然后,它使用自定义的join函数将这些子BlobPart与JSON结构中的分隔符(如:、,、[、]、{、}、换行符和缩进)组合起来。
    • 最后,它将这些组合后的BlobParts传递给new Blob([...]),创建一个新的Blob对象并返回。这样,一个大的JSON对象就被分解成了一个由许多小的Blob和字符串组成的层次结构。
  3. JSON.blobify(value, replacer, space) 函数:这是新的公共接口,类似于JSON.stringify。它负责初始化缩进和replacer逻辑,然后调用str函数从根对象开始进行序列化,并最终返回一个表示整个JSON内容的Blob对象。

使用示例

一旦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,
        *ailable: 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);
  }
})();

注意事项与总结

  1. 内存占用:尽管此方法避免了创建超长的中间字符串,但原始的J*aScript对象仍然完全加载在内存中。此方案主要解决的是JSON.stringify在将对象转换为字符串阶段的内存限制,而非整体内存占用问题。
  2. 性能:创建大量的Blob对象和执行递归操作可能会引入一定的性能开销。对于特别巨大的JSON对象,这可能比一次性stringify慢,但它解决了无法处理的问题。
  3. 浏览器兼容性:Blob API在现代浏览器中得到了广泛支持。此解决方案依赖于标准的Blob构造函数行为。
  4. 流式处理:此方案适用于内存中已有的POJO。如果你的数据源本身就是流式的(例如,从网络接收数据),那么更适合使用专门的流式JSON解析/序列化库(如json-stream-stringify,但通常用于Node.js环境或需要更复杂流处理的场景),而不是将整个对象加载到内存中。
  5. 调试:由于最终结果是嵌套的Blob结构,直接查看Blob内容不如查看字符串直观。但在需要时,可以通过Blob.text()方法将其转换为字符串进行检查。

通过这种分块构建Blob的策略,我们成功规避了浏览器对J*aScript字符串长度的限制,使得在浏览器端处理和存储超大型JSON数据成为可能。这为Web应用程序在处理大数据集时提供了更大的灵活性和鲁棒性。

以上就是将大型JSON对象高效转换为Blob以规避字符串长度限制的详细内容,更多请关注其它相关文章!


# 可以通过  # seo网站快  # 青州网站优化推广公司  # 网站推广公司报价标准最新  # 黄陂seo优化  # 金华品质网站建设费用  # 推广网站新手教程  # 网站数据搜索优化  # 德化家具网站推广  # 哪里找seo推广网站  # 核桃营销策划推广PPT  # 这是  # 而非  # 是一个  # 的是  # 自定义  # javascript  # 如何用  # 序列化  # 转换为  # 递归  # switc  # ai  # app  # 浏览器  # 大数据  # go  # node  # json  # node.js  # js  # java 


相关栏目: 【 科技资讯46185 】 【 网络学院92790


相关推荐: c++如何使用std::memory_order控制原子操作顺序_c++ C++11内存模型详解  抖音极速版最新版本 抖音极速版官方下载地址  mc.js官网登录入口 mc.js官方登录入口最新版  移动端XML文件怎么转换成Excel 手机和平板上的解决方案  Spring Boot内嵌服务器与J*a EE全栈特性:选择与部署策略  LINUX的I/O重定向是什么_深入理解LINUX中 >、>> 与 < 的区别  企业名称高精度匹配:N-gram方法在结构相似性分析中的应用  网易大神怎么保存别人动态的图片_网易大神动态图片保存方法  PowerPoint如何制作滚动字幕结尾彩蛋_PowerPoint路径动画实现平滑滚动字幕效果  如何使 Jest 模拟函数默认抛出错误以提高测试效率  怎么在浏览器上运行HTML文件_浏览器运行HTML文件技巧【技巧】  Lar*el Excel导入时生成自定义递增ID的策略与实践  Python自定义类排序:解决lambda键值访问TypeError的实践指南  Golang如何通过reflect获取匿名字段方法_Golang reflect匿名字段方法访问技巧  AO3最新可访问网址 Archive of Our Own官方在线入口  ArrayList与LinkedList核心操作的Big-O复杂度分析  汽水音乐在线解析 汽水音乐在线解析入口  Android Studio计算器C键逻辑错误排查与修复:条件判断优化指南  12306怎么选座位选到安静区_12306选座安静区域选择策略  火锅吃太多会怎样 火锅吃太多会上火吗  C++如何检测键盘输入_C++ _kbhit与_getch函数非阻塞输入  文本文档写html代码怎么运行_文本文档html代码运行步骤【教程】  探索高级语言到C/C++的转译路径:以Go为例及内存管理策略  新三国志曹操传110级星符试炼夏侯渊极难攻略  蛙漫画网页版全站入口 蛙漫热门作品免费浏览  c++如何使用Meson构建系统_c++比CMake更快的构建工具  Lar*el 8 多关键词数据库搜索优化实践  小米14应用无法联网原因分析_小米14网络权限修复  顺丰快递查单号物流信息 顺丰快递小程序查询入口  vivo手机互传视频怎么操作_vivo手机互传视频详细传输方法  J*aScript实现单选按钮与关联输入框的联动禁用教程  如何使用CaptainHook和Composer管理Git钩子_在提交前自动运行代码检查的Composer配置  J*aScript数组对象转换:按指定键分组与值收集  夸克浏览器桌面版同步不了书签怎么处理 夸克浏览器跨设备同步异常解决方案  PySpark中高效提取字符串右侧可变长度数字:使用regexp_extract  BetterDiscord插件中安全更新用户简介的实践指南  如何在Promise链中优雅地中断后续then执行  晋江读书网页版在线登录 晋江读书电脑版官网  文心一言怎样用插件调度API数据_文心一言用插件调度API数据【API调用】  如何优雅地解决Livewire文件上传难题?SpatieLivewireFilepond让一切变得简单  高德地图沿途添加点失败如何解决 高德多点规划方法  Golang如何实现微服务鉴权与权限控制_Golang微服务鉴权与权限管理实践  J*a递归快速排序中静态变量的状态管理与陷阱  J*a TimerTask文件监控:HashMap状态管理与常见陷阱规避指南  抖音商城签到领现金是真的吗_抖音商城签到奖励与提现说明  快手官方唯一登录入口 谨防山寨钓鱼网站  C++如何实现一个智能指针_手动实现C++ shared_ptr的引用计数功能  飞书妙记怎样用语音转文字速记_飞书妙记用语音转文字速记【速记方法】  CSS布局:解决全屏元素100%尺寸与外边距导致的页面溢出问题  从OpenAI API响应中高效提取生成文本 

搜索