For AI agents: the complete documentation index is available at /tc39-atlas/llms.txt, the full documentation bundle is available at /tc39-atlas/llms-full.txt, and this page is available as Markdown at /tc39-atlas/proposals/year/2026/proposal-json-parse-with-source.md.
  • 简体中文
  • JSON.parse source text access S4

    中文标题:JSON.parse 源文本访问

    提案概览
    提案速览

    本提案解决了 ECMAScript 值与 JSON 文本之间转换有损的问题,特别是对于数字和像 BigInt 这样的特殊对象。它扩展了 JSON.parse,向 reviver 函数传递源文本和上下文,并添加了 JSON.rawJSON 以实现无损失真。

    Note

    以下 README 来自上游仓库,其中的阶段或状态标注可能滞后;当前信息以提案概览为准。

    JSON.parse 源文本访问

    本提案旨在扩展 JSON.parse 行为,使 reviver 函数能够访问输入的源文本,并扩展 JSON.stringify 行为,以支持用于原始 JSON 文本原语的对象占位符。

    2023年9月幻灯片

    2018年9月原始幻灯片

    状态

    本提案处于 TC39 流程 的第 4 阶段。

    champion

    • Richard Gibson
    • Mathias Bynens

    动机

    ECMAScript 值与 JSON 文本之间的转换是有损的。 这在反序列化数字时最为明显(例如,"999999999999999999""999999999999999999.0""1000000000000000000" 都解析为 1000000000000000000),但在尝试往返非原始值(如 Date 对象)时也会出现(例如,JSON.parse(JSON.stringify(new Date("2018-09-25T14:00:00Z"))) 产生字符串 "2018-09-25T14:00:00.000Z")。

    这些示例都不是假设性的——将 BigInt 作为 JSON 序列化被指定为抛出异常,因为没有输出能够通过 JSON.parse 往返,类似的概念也已在 Temporal 提案 中提出。

    JSON.parse 接受一个能够处理传入值的 reviver 函数,但它自底向上调用,并且收到的上下文极少(一个 key、一个已经丢失信息的 value,以及一个接收者,其中 key 是属性值为 value 的自身属性),因此实际上几乎无用。 我们打算解决这个问题。

    提议的解决方案

    更新 JSON.parse,为 reviver 函数提供更多参数,主要传递值所来源的源文本(包含标点,但不包含前导/尾随的无意义空白)。

    序列化

    虽然最初未包含在本提案中,但已请求并添加了对 JSON.stringify 的无损序列化支持(从而也实现了完整的往返能力)(参见 #12),目前使用特殊的"原始 JSON"冻结对象,可通过 JSON.rawJSON 构造,但可能会更改(参见 #18#19)。

    示例说明

    const digitsToBigInt = (key, val, {source}) =>
      /^[0-9]+$/.test(source) ? BigInt(source) : val;
    
    const bigIntToRawJSON = (key, val) =>
      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
    
    const embedded = JSON.stringify({ tooBigForNumber }, bigIntToRawJSON);
    embedded === '{"tooBigForNumber":9007199254740993}';
    // → true

    可能的增强

    暴露位置和输入信息

    String.prototype.replace 将位置和输入参数传递给替换函数,而 RegExp.prototype.exec 的返回值具有 "index" 和 "input" 属性;JSON.parse 可以类似地行为。

    const input = '\n\t"use\\u0020strict"';
    let spied;
    const parsed = JSON.parse(input, (key, val, context) => (spied = context, val));
    parsed === 'use strict';
    // → true
    spied.source === '"use\\u0020strict"';
    // → true
    spied.index === 2;
    // → true
    spied.input === input;
    // → true
    
    提供键的数组以理解值上下文

    reviver 函数自底向上查看值,但数据结构层次结构已知,可以提供给它,无论是否带有幻影前导空字符串。

    const input = '{ "foo": [{ "bar": "baz" }] }';
    const expectedKeys = ['foo', 0, 'bar'];
    let spiedKeys;
    JSON.parse(input, (key, val, {keys}) => (spiedKeys = spiedKeys || keys, val));
    expectedKeys.length === spiedKeys.length;
    // → true
    expectedKeys.every((key, i) => spiedKeys[i] === key);
    // → true

    实现

    讨论

    向后兼容性

    符合规范的 ECMAScript 实现不被允许扩展 JSON.parse 接受的语法。 本提案并未尝试这样做,添加新的函数参数是对语言最安全的更改之一。 所有当前被 JSON.parse 拒绝的输入将继续被拒绝,所有当前被接受的输入将继续被接受,并且对其输出的唯一更改将由用户代码直接控制。

    修改后的值

    reviver 函数旨在修改或移除输出中的值,但这些更改不应影响传递给它们的基于源文本的参数。 由于 reviver 函数自底向上调用,这意味着值可能无法与源文本对应。 我们认为这是可以接受的,但大多已无关紧要(参见下一点)。在并非无关紧要的情况下(例如当尚未访问的数组索引或对象条目被修改时),源文本会被抑制。

    非原始值

    根据 https://github.com/tc39/proposal-json-parse-with-source/issues/10#issuecomment-704441802 ,源文本暴露仅限于原始值。