在浏览器和 Node.js 中,JSON.parse 是最常用的解析 JSON 字符串的方法,但它有一个明显特点:只要字符串不符合 JSON 规范,就会立即抛出 SyntaxError。这种抛错如果发生在一个较大的数据处理流程里,又没有捕获机制,后续脚本会直接中断,用户侧表现可能就是页面无响应或接口数据渲染空白。除了语法错误,JSON.parse 返回的对象还可能带有 __proto__ 这类特殊键,如果不做防护,在旧版本运行时或某些合并操作中可能带来原型污染风险。围绕这些问题,下面会分别从异常捕获、解析前预检、封装式安全解析三个角度展开,并给出可直接使用的代码示例。

一、用 try...catch 捕获 JSON.parse 异常
最直接的安全处理方式是给 JSON.parse 包一层 try...catch。当字符串不合法时,JSON.parse 会抛出 SyntaxError,catch 块可以捕获错误、记录日志,并返回一个兜底值,避免异常向上传播。比如接口返回的 JSON 字符串偶尔因为服务端拼接错误少了一个右花括号,或者在本地缓存里读到了被截断的内容,这时兜底值通常可以选择 null、空对象或调用方传入的默认值。但要注意,兜底值不能随意返回 null,否则调用方后续访问 data.list 时仍然会触发 TypeError。
下面是一个基础封装,解析失败时默认返回空对象,并允许调用方传入默认值:
function safeParse(jsonStr, fallback = {}) {
try {
return JSON.parse(jsonStr);
} catch (error) {
console.warn('JSON 解析失败:', error.message);
return fallback;
}
}
const result = safeParse('{"name":"kevin"}', { name: '匿名用户' });
console.log(result.name); // kevin
这个方案的优势是简单、零依赖,几乎所有场景都能快速加上。但它只能解决“解析不抛错”的问题,并不能保证返回的数据结构一定符合业务预期。例如字符串 'null' 或 '123' 是合法 JSON,JSON.parse 会返回 null 或数字 123,而不是对象。如果后续代码假设返回值一定有 length 属性或直接使用 Object.keys,仍可能产生二次异常。因此异常捕获适合作为第一道防线,接下来还需要格式与类型层面的预检。
二、解析前进行格式检查和类型清洗
在调用 JSON.parse 之前先做一次轻量校验,可以减少无意义的异常抛出,也能更早发现数据类型问题。首先要确认入参确实是字符串,因为 JSON.parse 对非字符串参数会先做类型转换,例如传入数字 123 会被转成字符串 '123' 并返回数字 123,这个行为有时与预期不符。可以通过 typeof jsonStr !== 'string' 直接拦截。其次要处理空字符串和纯空白字符,因为 JSON.parse('') 会抛出 SyntaxError,而实际业务中很可能从输入框或缓存中拿到空值。
一个常见的预检封装如下:
function parseJSONWithCheck(raw, fallback = {}) {
if (typeof raw !== 'string') {
console.warn('解析目标不是字符串');
return fallback;
}
const trimmed = raw.trim();
if (trimmed.length === 0) {
console.warn('传入空字符串');
return fallback;
}
// 简单排除明显不是 JSON 对象/数组的内容
const firstChar = trimmed[0];
if (firstChar !== '{' && firstChar !== '[') {
console.warn('JSON 字符串必须以 { 或 [ 开头');
return fallback;
}
try {
return JSON.parse(trimmed);
} catch (error) {
console.warn('JSON 格式不合法:', error.message);
return fallback;
}
}
这段代码先做类型判断,再剔除首尾空格,然后只允许以 { 或 [ 开头的字符串进入解析流程。这样可以拦住 'abc'、'123'、'true' 这类 JSON 标量值,对大多数接口返回对象或数组的场景更安全。不过要注意,这种检查只是一个前置过滤,不能代替 JSON 解析本身。曾经有人尝试用正则表达式完整校验 JSON 字符串,但 JSON 支持嵌套、转义、Unicode 字符等复杂情况,手写正则很难完全覆盖,反而容易漏掉边界条件。既然最终仍需调用 JSON.parse,那么前置检查的目标就应该是快速过滤明显错误,而不是实现一个完整解析器。
另一个容易被忽略的细节是 BOM 头。某些编辑器或接口返回的 JSON 字符串可能带有 \uFEFF 字符,直接交给 JSON.parse 会报错。在预检阶段可以使用 trimmed.replace(/^\uFEFF/, '') 移除 BOM 字符,这样能兼容更多数据来源。
三、用封装函数统一处理 reviver 与原型污染防护
即使字符串合法,解析结果也可能带有敏感键。JSON 标准允许对象中出现 __proto__、constructor、prototype 这样的键,而 JSON.parse 在大多数现代 JavaScript 引擎中不会直接触发原型污染,因为它们会把这些键当成普通自有属性处理。但如果解析后的对象被用于深度合并、递归赋值,或者运行在依赖旧版本 lodash 等工具的环境里,污染风险仍然存在。更稳妥的做法是在解析时通过 reviver 函数过滤这些键,或在解析后把对象转换为完全无原型的结构。
JSON.parse 的第二个参数 reviver 会在每个键值对创建后调用,我们可以利用它拦截特殊键:
const BLOCKED_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
function safeJSONParse(jsonStr, fallback = {}) {
try {
return JSON.parse(jsonStr, (key, value) => {
if (BLOCKED_KEYS.has(key)) {
return undefined; // 返回 undefined 会删除该键
}
return value;
});
} catch (error) {
console.error('安全解析失败:', error.message);
return fallback;
}
}
const obj = safeJSONParse('{"name":"kevin","__proto__":{"polluted":true}}');
console.log(obj.__proto__ === Object.prototype); // true
console.log(obj.polluted); // undefined
返回 undefined 的键会被 JSON.parse 从结果中移除,所以上面例子中的 __proto__ 不会影响最终对象。除了过滤敏感键,reviver 还可以做类型转换、日期恢复、数字边界检查等。比如后端把时间戳放在 createdAt 字段里,你可以在 reviver 中判断键名,把合法的数值转成 Date 对象。但要注意,reviver 会递归遍历所有键,数据量很大时会有一定性能开销,因此不要在 reviver 里做过于复杂的计算。
如果业务层完全不想让解析结果带有原型链,可以在拿到对象后用 Object.assign(Object.create(null), parsed) 做一次浅拷贝,但这样会丢失数组的方法。更合理的做法是根据数据类型分别处理:对象转成 Object.create(null) 上的属性,数组仍然保留 Array.prototype。不过大多数常规业务并不需要这么极端,使用 reviver 过滤敏感键已经能覆盖主要风险。
四、三种方式的适用场景与组合建议
如果只是解析一个来源相对可靠的接口数据,第一层的 try...catch 已经能避免大部分崩溃。它写起来最快,适合快速兜底。第二层的格式预检更适合接收用户输入、读取本地缓存、处理粘贴的配置内容等来源不可控的场景,能提前过滤空值、非字符串和明显不是对象的输入。第三层的封装式安全解析则偏向长期维护的公共工具函数,尤其是当解析结果会被多个模块复用、写入全局配置、参与对象合并时,过滤敏感键和统一默认值的价值更高。
这三种方式并不冲突,实际项目中可以组合使用。例如先判断类型和空值,再做一次轻量格式检查,最后调用带 reviver 的 try...catch 解析函数。组合后的代码大致如下:
function createSafeParser(defaultValue = {}) {
const blocked = new Set(['__proto__', 'constructor', 'prototype']);
return function parse(raw) {
if (typeof raw !== 'string') return defaultValue;
const text = raw.trim().replace(/^\uFEFF/, '');
if (!text) return defaultValue;
const first = text[0];
if (first !== '{' && first !== '[') return defaultValue;
try {
return JSON.parse(text, (key, value) => {
if (blocked.has(key)) return undefined;
return value;
});
} catch (error) {
console.warn('JSON 解析失败:', error.message);
return defaultValue;
}
};
}
const parseConfig = createSafeParser({ enabled: false });
const config = parseConfig('{"enabled":true,"__proto__":{"bad":1}}');
console.log(config.enabled); // true
console.log(config.bad); // undefined
这个工厂函数把类型检查、空值处理、BOM 移除、首字符判断、异常捕获和敏感键过滤放在一个流程里。调用方只需要传入一个默认值,就能得到稳定、安全的解析结果。需要注意的是,默认值最好在每次调用时返回同一个引用,或者使用工厂函数创建新的默认对象,否则多个调用方拿到同一个兜底对象后,可能在一处修改影响到另一处。
最后还要强调,安全解析 JSON 不代表解析后的数据一定可信。如果这些数据会进入 DOM、SQL 语句或 eval 类执行环境,仍然需要根据具体场景做转义、参数化查询或白名单校验。JSON 解析安全解决的是“格式是否合法、结构是否可控、异常是否被捕获”的问题,而数据内容本身的安全要交给下游的防御机制处理。
JSON.parseJavaScript异常处理修改时间:2026-09-26 21:26:44