← 全部文章

如何修复 JSON.parse 的 “Unexpected Token” 错误

“Unexpected token <” 或 “Unexpected token u in JSON at position 0” —— 这类错误会让应用直接停摆。本文讲清楚每个变体的含义,以及具体如何修。

你正盯着一段红色的堆栈跟踪:SyntaxError: Unexpected token '<', "..." is not valid JSON。或者是 Unexpected token u in JSON at position 0。无论是哪一种,JSON.parse() 都拒绝执行,你的代码也停了。本指南讲清楚这个错误每一种变体到底是什么意思、为什么发生、以及如何快速修复。

错误真正长什么样

措辞因 JavaScript 引擎而异,但本质都是一回事:parser 在字符串里某个具体位置撞到了它不期望出现的字符。

// V8 (Chrome, Node.js, Edge)
SyntaxError: Unexpected token '<', "<html>..." is not valid JSON
SyntaxError: Unexpected token 'u', "undefined" is not valid JSON
SyntaxError: Unexpected token u in JSON at position 0  // 较老的 V8
SyntaxError: Expected ',' or '}' after property value in JSON at position 42

// Firefox (SpiderMonkey)
SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON data
SyntaxError: JSON.parse: unexpected non-whitespace character after JSON data

// Safari (JavaScriptCore)
SyntaxError: JSON Parse error: Unexpected identifier "undefined"
SyntaxError: JSON Parse error: Single quotes (') are not allowed in JSON

关键信息总是两部分:什么字符不该出现在哪儿。字符告诉你 parser 实际收到了什么;位置告诉你去字符串的哪个位置看。

原因 1:服务器返回了 HTML 而不是 JSON

这是 Unexpected token '<' 最常见的原因。你的 fetch 或 AJAX 调用拿回了一份 HTML 页面 —— 通常是 404 页、登录跳转或服务器错误 —— 而不是你期望的 JSON。那个 < 就是 <!DOCTYPE html><html> 的开头标签。

// ❌ 这会以 "Unexpected token '<'" 崩溃
const res  = await fetch('/api/user');
const data = await res.json(); // 服务器返回了 HTML 404

// ✅ 先检查响应状态
const res = await fetch('/api/user');
if (!res.ok) {
  throw new Error(`HTTP ${res.status}: ${res.statusText}`);
}
const data = await res.json();

调用 res.json() 前永远先检查 res.ok(或 res.status)。你也可以先看一眼原始响应文本:

const text = await res.text();
console.log(text.slice(0, 200)); // 看看你到底拿到的是什么
const data = JSON.parse(text);

原因 2:值是 undefined

Unexpected token 'u' 在位置 0 几乎总是意味着你调用了 JSON.parse(undefined)。字符串 "undefined" 以字母 u 开头,而 u 不是合法的 JSON 起始字符。

// ❌ 变量从来就没被赋值
const raw = localStorage.getItem('settings'); // 不存在时返回 null
JSON.parse(raw);  // null 没问题,但如果 raw 不知怎么是 undefined...

// ❌ async 数据还没加载完
JSON.parse(this.state.data); // 首次渲染时 data 是 undefined

// ✅ 解析前先 guard
if (raw) {
  const data = JSON.parse(raw);
}

注意 localStorage.getItem() 对不存在的 key 返回的是 null(不是 undefined),而 JSON.parse(null) 会无错地返回 null。常见元凶是未初始化的 state 变量或缺失的函数参数。

原因 3:用了单引号而不是双引号

// ❌ SyntaxError: Unexpected token '''
JSON.parse("{'name': 'Ada'}");

// ✅ JSON 必须用双引号
JSON.parse('{"name": "Ada"}')

JavaScript 对象字面量接受单引号。JSON 不行 —— 所有字符串(包括 key)都必须用双引号。常见情况是有人用 Python 的 str() 而不是 json.dumps() 来序列化数据,或者直接复制了一段对象字面量。

原因 4:尾随逗号

// ❌ SyntaxError: Unexpected token '}'
JSON.parse('{"name": "Ada", "score": 98,}');
//                                       ^ 尾随逗号

// ✅
JSON.parse('{"name": "Ada", "score": 98}')

现代 JavaScript 允许数组和对象里有尾随逗号。JSON 不允许。手工编辑的 JSON 配置文件是这个错误最常见的来源。

原因 5:没加引号的 key

// ❌ SyntaxError: Unexpected token 'n'
JSON.parse("{name: 'Ada'}");

// ✅
JSON.parse('{"name": "Ada"}')

JSON 里的对象 key 必须始终是双引号字符串。像 name 这种裸标识符在 JavaScript 对象字面量里合法,在 JSON 里不行。

原因 6:JSON 里有注释

// ❌ SyntaxError: Unexpected token '/'
JSON.parse(`{
  // 用户记录
  "name": "Ada"
}`);

// ✅ 解析前先去掉注释(或用一个 JSONC parser)
const stripped = raw.replace(///.*$/gm, '').replace(//*[sS]*?*//g, '');
JSON.parse(stripped);

JSON 没有注释语法。注释经常被加到配置文件里(这就是 JSONC 格式),但标准的 JSON.parse() 会立刻把它拒绝。

原因 7:位置 0 处有 BOM 或不可见字符

如果错误说在位置 0 但第一个可见字符看起来没问题,那可能字符串前面被加了一个不可见的 byte-order mark(BOM,U+FEFF)或其他控制字符 —— 通常是在 Windows 上读取以 UTF-8-BOM 编码保存的文件时出现。

// ✅ 解析前先去掉 BOM
const clean = raw.replace(/^/, '');
JSON.parse(clean);

原因 8:token IN —— InfinityNaN

如果错误说 Unexpected token IUnexpected token N,那是数据源产出了 Infinity-InfinityNaN —— 这些在 JavaScript 里都合法,但 在 JSON 里不合法

// ❌ 生产端的错误
const json = '{"ratio": NaN, "limit": Infinity}';
JSON.parse(json); // Unexpected token N (or I) ...

// ✅ 明确地编码它们
const safe = JSON.stringify({ ratio: NaN, limit: Infinity });
// → '{"ratio":null,"limit":null}'  (注意:有损 —— 都变成 null)

// ✅ 无损:编码成一个 sentinel 字符串,自己再解析回来
JSON.stringify({ ratio: 'NaN', limit: 'Infinity' });

JSON.stringify 会悄悄地把 NaN / Infinity 转成 null。如果你需要它们能往返编解码,就在输出时换成一个 sentinel 字符串,输入时再解析回来。

如何找到错误的精确位置

错误信息里带一个位置数字。用它去切字符串,看那个位置到底是什么:

function parseWithContext(text) {
  try {
    return JSON.parse(text);
  } catch (err) {
    // 从 V8 错误信息里抽出 position
    const match = err.message.match(/position (\d+)/);
    if (match) {
      const pos = Number(match[1]);
      const snippet = text.slice(Math.max(0, pos - 20), pos + 20);
      console.error(`Error near: ..."${snippet}"...`);
      console.error(`             ${''.padStart(Math.min(pos, 20), ' ')}^`);
    }
    throw err;
  }
}

常见问题

「Unexpected token in JSON」是什么意思?

它意味着 JSON.parse() 在语法里那个点位上撞到了一个非法字符。错误里点名的 token 告诉你收到了什么(< = HTML,u = undefined' = 单引号),位置告诉你去哪儿看。

为什么 fetch 时会出现「Unexpected token '<'」?

你的请求返回了一份 HTML 页面 —— 404、登录跳转或服务器错误 —— 而不是 JSON,那个 < 就是 HTML 的开头标签。调用 response.json() 之前先检查 response.ok,并 log 一下 await response.text() 看看到底返回了什么。

怎么修「Unexpected token u in JSON at position 0」?

你把 undefined 传给了 JSON.parse()。先 guard 这个值(if (raw) JSON.parse(raw)),并检查是不是有未初始化的 state 或缺失的函数参数。紧密相关的 token o 变体见 Unexpected token o in JSON

怎么找到错误的精确位置?

从错误信息里读出位置数字,按它去切字符串看上下文(见上面的代码片段),或者把 JSON 贴到 JSON Fix,它会高亮出出错的那一行。

最快的修法:在线粘贴修复

如果你手头有一段坏掉的 JSON 字符串、现在就想把它弄好,JSON Fix 能自动处理上面所有这些原因 —— 单引号、尾随逗号、没引号的 key、注释、Python 字面量等等。粘进去你的 JSON,点 Repair & Format,一步就能拿回干净合法的 JSON。一切都在你的浏览器里跑,不会发到任何服务器。