
core-js 中的 ECMAScript JSON 模块解析源码感知的 parse 与 rawJSON 原始值序列化【免费下载链接】core-jsStandard Library项目地址: https://gitcode.com/GitHub_Trending/co/core-jsJSON 是现代 JavaScript 生态中最基础的数据交换格式但原生JSON.parse/JSON.stringify在一些细节上并不符合最新标准例如parse的 reviver 拿不到数值的原始源码文本stringify无法无损输出超出Number.MAX_SAFE_INTEGER的整数旧引擎还会把孤立代理项lone surrogate输出成非法 Unicode。本文以 core-js 仓库的 docs/web/docs/features/ecmascript/json.md 为核心骨架结合 packages/core-js/modules/ 下的五个模块源码与 tests/unit-global/ 中的测试用例完整梳理 core-js 对JSON对象的修复范围、入口点、API 签名与实战用法帮助你掌握源码级 JSON 解析JSON.parse with source与JSON.rawJSON大整数无损序列化的落地写法。为什么 core-js 只修复而不重写 JSONcore-js 的目标是让代码在任何引擎上都能按最新 ECMAScript 标准运行但JSON对象属于例外现代引擎IE8 乃至更早的浏览器几乎都实现了JSON.parse与JSON.stringify只有 IE7 及更早的极老环境才完全缺失该对象。因此文档明确说明SinceJSONobject is missed only in very old engines like IE7-,core-jsdoes not provide a fullJSON.{ parse, stringify }polyfill, however, fix already existing implementations by the current standard.即 core-js不提供完整的 JSON polyfill而是按当前标准修补既有实现的缺陷。这是所有使用JSON相关模块时需要记住的前提它修复的是行为与最新规范不一致的部分而不是从零实现解析器。从源码结构也可以印证这一点——es.json.parse.js 中直接取出globalThis.JSON.parse作为nativeParse基准es.json.stringify.js 中$stringify getBuiltIn(JSON, stringify)都以引擎原生实现 条件修复为设计思路。具体而言core-js 需要修复的问题包括三大类JSON.parse 的 reviver 缺少 source 参数JSON.parse with source 提案reviver 只能拿到被引擎舍入后的数值无法还原原始文本JSON.stringify 无法序列化 BigInt / 超过安全整数的数值缺少 JSON.rawJSON 机制时大整数会被写坏或直接报错旧引擎的字符串化细节不符合规范例如孤立代理项输出、Symbol 值转换差异MS Edge 将 Symbol 转成{}、WebKit 转成null、V8 对装箱 Symbol 抛错等。模块清单与入口点涉及的五个核心模块文档列出 core-js 提供的 JSON 相关模块对应 packages/core-js/modules/ 下的实现模块源码文件作用es.json.is-raw-jsones.json.is-raw-json.js提供JSON.isRawJSON判断对象是否为 raw JSON 值es.json.parsees.json.parse.js按 JSON.parse with source 规范重写JSON.parsereviver 可拿到context.sourcees.json.raw-jsones.json.raw-json.js提供JSON.rawJSON构造携带原始文本的 RawJSON 对象es.json.stringifyes.json.stringify.js修补JSON.stringify支持 raw JSON 值、修正代理项与 Symbol 转换es.json.to-string-tages.json.to-string-tag.js设置JSON[toStringTag] JSON其中es.json.to-string-tag对应Object.prototype.toString.call(JSON) [object JSON]的标准化实现于 set-to-string-tag 配套逻辑其余四个模块共同构成JSON.parse with source rawJSON这一能力集。Entry points 使用方式core-js 的所有模块都有统一的入口点体系。文档给出 JSON 相关的入口点写法core-js(-pure)/es|stable|actual|full/json/is-raw-json core-js(-pure)/es|stable|actual|full/json/parse core-js(-pure)/es|stable|actual|full/json/raw-json core-js(-pure)/es|stable|actual|full/json/to-string-tag其含义是包名可以是core-js污染全局或core-js-pure纯模块、不修改全局对象目录层级es/stable/actual/full代表不同范围的特性集合es为已定稿标准特性stable/actual/full逐渐放宽到包含提案阶段特性末尾的json/xxx对应具体特性。用法例如// 按需引入仅修复 JSON.parse附带 parse-with-source 能力 require(core-js/es/json/parse); // 引入 raw JSON 相关全部能力 require(core-js/full/json/raw-json); require(core-js/full/json/stringify);也可以直接用聚合入口一次引入所有 JSON 相关模块require(core-js/es/json);该聚合入口在 packages/core-js/es/json/index.js 中定义它会依次加载es.json.is-raw-json、es.json.parse、es.json.raw-json、es.json.stringify与es.json.to-string-tag并额外前置加载es.object.create、es.object.freeze、es.object.keys、es.date.to-json等依赖模块rawJSON 对象的创建、冻结与遍历依赖这些基础能力。入口点完整说明可参见仓库文档 docs/web/docs/usage.md 的 Entry points 一节。API 签名文档给出标准 TypeScript 签名如下namespace JSON { isRawJSON(O: any): boolean; parse(text: string, reviver?: (this: any, key: string, value: any, context: { source?: string }) any): any; rawJSON(text: any): RawJSON; stringify(value: any, replacer?: Arraystring | number | (this: any, key: string, value: any) any, space?: string | number): string | void; toStringTag: JSON; }逐个解读isRawJSON(O)返回布尔值判断O是否为JSON.rawJSON创建的 RawJSON 对象。非对象直接返回false见 internals/is-raw-json.js。parse(text, reviver?)第二个参数reviver的第四个参数扩展为context其中context.source为该属性在源文本中的原始字符串仅对原始 JSON 值有意义详见下文。rawJSON(text)把任意值内部先toString包装成一个 RawJSON 对象返回的RawJSON带有rawJSON属性保存原始文本。stringify(value, replacer?, space?)与标准签名一致但额外感知 RawJSON 对象——遇到JSON.rawJSON返回值时按其中保存的原始文本输出而不是当作普通对象序列化。toStringTagJSON由es.json.to-string-tag保证。实战示例大整数与 surrogate 的安全处理文档中的示例完整演示了这两个能力如何配合解决JSON 无法精确表达大整数的实际问题function digitsToBigInt(key, val, { source }) { return /^\d$/.test(source) ? BigInt(source) : val; } function bigIntToRawJSON(key, val) { return typeof val bigint ? JSON.rawJSON(String(val)) : val; } const tooBigForNumber BigInt(Number.MAX_SAFE_INTEGER) 2n; JSON.parse(String(tooBigForNumber), digitsToBigInt) tooBigForNumber; // true const wayTooBig BigInt(1${ 0.repeat(1000) }); JSON.parse(String(wayTooBig), digitsToBigInt) wayTooBig; // true JSON.stringify({ tooBigForNumber }, bigIntToRawJSON); // {tooBigForNumber:9007199254740993} JSON.stringify({ : [\uDF06\uD834] }); // {:[\\udf06\\ud834]}拆解其含义parse 侧还原大整数9007199254740993Number.MAX_SAFE_INTEGER 2在原生JSON.parse中会被舍入为9007199254740992。core-js 修复后的parse会把该值的原始源码文本通过context.source交给 reviverdigitsToBigInt据此用BigInt(source)精确还原1后面跟 1000 个0的超大整数同样成立。这正是 JSON.parse with source 提案的核心价值。stringify 侧保留原始文本bigIntToRawJSON在 reviver 中遇到 BigInt 时用JSON.rawJSON(String(val))包一层stringify遇到 RawJSON 对象时直接输出其原始文本9007199254740993不带引号从而避免 BigInt 无法被原生 stringify 处理的尴尬。字符串孤立代理项转义\uDF06\uD834是反向排列的代理项对原生stringify在旧引擎中会输出非法 Unicode浏览器解码时替换为 。core-js 的修复会将其转义为\\udf06\\ud834保证输出为合法 JSON对应源码中fixIllFormedJSON与ILL_FORMED_UNICODE检测见 es.json.stringify.js。源码级原理JSON.parse 如何提供 context.sourcees.json.parse.js 是这次修复中最具技术含量的一份源码当检测到原生parse的 reviver 拿不到context.sourceNO_SOURCE_SUPPORT检测失败见 es.json.parse.js时core-js 会完全替换JSON.parse用自研的语法分析器重解析整个 JSON 文本。该解析器的结构es.json.parse.js清晰可读Context持有source字符串与当前位置index提供parse、object、array、string、number、keyword等递归下降解析方法Node记录每个值的value、结束位置end、原始源码source与子节点nodesnode()工厂在PRIMITIVE类型上保存源码切片es.json.parse.jsinternalize遍历解析出的对象/数组在每个属性上调用 reviver 并传入contextcontext.source仅在值未被 reviver 修改unmodified且拥有字符串形式的source时才填充es.json.parse.js避免暴露无关文本数值解析严格按 json.org 语法 处理负号、前导零、小数与指数非数字字符、未闭合对象/数组、多余的尾部字符都会抛出带位置信息的SyntaxError。一个值得注意的实现细节number()解析es.json.parse.js对0前导与1.这类非法小数片段单独报错测试在 tests/unit-global/es.json.parse.js 中有大量针对空白字符TAB、CR、LF、SP 以及\u000b、\u000c、\u00a0、\ufeff等非 JSON 空白的边界用例例如parse(12\t34)必须抛SyntaxError两个 token 被空白分隔。这些用例同时被 QUnit 的assert.arity(parse, 2)、assert.name(parse, parse)约束了 API 形态。而JSON.parse的模块导出逻辑es.json.parse.js显示当引擎本身行为正确PROPER_BASE_PARSE且没有传入 reviver 时直接走nativeParse(text)原生快路径只有传了 reviver 或引擎有缺陷时才走完整$parse路径。源码级原理JSON.rawJSON 与 stringify 的写入流程rawJSON 的构造与校验es.json.raw-json.js 中的JSON.rawJSON做了三层校验输入先toString首尾字符不允许是 JSON 空白、\t、\n、\r空字符串也直接抛SyntaxError(Unacceptable as raw JSON)必须能被JSON.parse解析为原始值不能是对象或数组否则同样抛错。满足条件后它创建一个无原型对象Object.create(null)通过内部状态槽标记type: RawJSON并把原始文本存入rawJSON属性最后在FREEZING配置开启时冻结该对象。对应的JSON.isRawJSON实现internals/is-raw-json.js正是读取该内部状态槽判断类型。测试用例tests/unit-global/es.json.raw-json.js验证了各种合法/非法输入rawJSON(qwe)序列化为qwe、rawJSON(9007199254740993)序列化为9007199254740993而rawJSON(qwe)未闭合字符串、rawJSON({})对象、rawJSON()空串都会抛SyntaxError。stringify 如何插入原始文本es.json.stringify.js 的修复逻辑绕开了引擎不认识 RawJSON 对象的问题stringify在 reviver 回调中捕获 RawJSON 值替换成一个内部占位符RAW_MARK 索引并推迟到最终结果字符串生成完毕后用重新扫描 JSON 字符串、替换占位符的方式把原始文本写回es.json.stringify.js。这保证rawJSON(9007199254740993)输出为不带引号的9007199254740993而不是被当作字符串输出。该模块还顺带处理了两类引擎差异同样通过fails()特性检测按需启用WRONG_SYMBOLS_CONVERSIONes.json.stringify.js不同引擎对 Symbol 值的 stringify 行为不一致{}、null或抛错core-js 用代理 reviver 统一为规范行为[null]/{}ILL_FORMED_UNICODEes.json.stringify.js孤立代理项输出修复即上文示例中的:[\\udf06\\ud834]。此外stringify对 replacer 数组PropertyList做了getPropertyList规范化去重、数字/装箱类型转字符串es.json.stringify.js并通过createOrderedObject与createElementHolder处理属性顺序与惰性取值从源码结构看是为了兼容按 replacer 顺序输出与循环引用检测Converting circular structure to JSON等边界场景。使用建议与边界说明按需引入仅在需要 parse-with-source 或 rawJSON 能力的代码路径引入core-js/es/json/parse、core-js/full/json/raw-json等模块若只用标准 JSON 功能现代引擎本就满足需求无需引入。BigInt 序列化的完整闭环JSON.rawJSON目前是提案阶段特性对应 TC39 proposal-json-parse-with-source因此入口建议使用core-js/full/...路径以包含提案能力实际使用时stringify的 reviver 中负责把 BigInt 转成 raw JSON 文本parse的 reviver 中负责把纯数字源码还原回 BigInt两者成对使用才能做到无损往返。rawJSON 的合法性约束JSON.rawJSON只接受可解析为原始值且无首尾空白的文本对象/数组形式必须显式序列化后再包一层这是设计使然——它只承诺原始值的无损表示。适用范围文档与实现均针对既有 JSON 实现的修复IE7 及更早环境不在讨论范围core-js 的es.json.*模块聚焦最新标准的差距补齐而非提供旧环境的全量 JSON polyfill。如需深入源码建议按以下路径阅读先读 es.json.parse.js 掌握递归下降解析器与context.source的产生再读 es.json.stringify.js 理解占位符替换与代理项修复最后用 tests/unit-global/es.json.raw-json.js、tests/unit-global/es.json.parse.js 中的用例验证各边界行为。【免费下载链接】core-jsStandard Library项目地址: https://gitcode.com/GitHub_Trending/co/core-js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考