[{"data":1,"prerenderedAt":200},["ShallowReactive",2],{"article-json-unexpected-token":3},{"article":4,"relatedArticles":152},{"slug":5,"category":6,"date":7,"readingMinutes":8,"tags":9,"relatedTool":15,"cn":16,"en":81,"author":140,"reviewedBy":143,"reviewedDate":145,"updatedDate":7,"sources":146,"noIndex":151},"json-unexpected-token","错误排查","2026-07-28",10,[10,11,12,13,14],"JSON","JSON.parse","SyntaxError","Unexpected token","调试","/tools/json/json-validator",{"title":17,"description":18,"intro":19,"sections":20},"JSON Unexpected token：8 类原因与定位","从原始响应、错误位置和 JSON 语法三层定位 JSON.parse 的 Unexpected token，覆盖 HTML 响应、重复解析、尾逗号、引号、控制字符、非法数字和截断数据。","JSON.parse 抛出 Unexpected token、unexpected character 或 bad parsing 时，真正有用的信息不是固定报错文案，而是“输入究竟是什么、解析器在哪个位置停止、该位置本应出现什么”。不同浏览器和 Node.js 版本的错误文字可能不同，因此本文以可复现的输入类型和定位流程为主，不提供可能破坏数据的通用自动修复正则。",[21,27,40,46,51,57,63,73],{"heading":22,"paragraphs":23,"code":26},"一、先确认传给 JSON.parse 的确实是字符串",[24,25],"JSON.parse 会先把非字符串参数转换为字符串。把已经解析好的对象再次传入时，对象通常会变成 [object Object]，随后在开头附近报错。这个问题不是 JSON 内容损坏，而是调用层级重复。","入口处先检查 typeof。若数据来自 fetch，response.json() 已经完成解析；不要再对返回对象调用 JSON.parse。","const objectValue = { id: 1 };\n\ntry {\n  JSON.parse(objectValue as unknown as string);\n} catch (error) {\n  console.error(error); // 各运行时的具体文案可能不同\n}\n\nfunction parseJsonText(input: unknown): unknown {\n  if (typeof input !== 'string') {\n    throw new TypeError(`Expected JSON text, received ${typeof input}`);\n  }\n  return JSON.parse(input);\n}",{"heading":28,"paragraphs":29,"bullets":31},"二、8 类高频原因应按输入来源分类",[30],"语法错误只是其中一类。接口调试时，应先区分“拿到的根本不是 JSON”和“拿到了 JSON 形状但语法非法”，否则容易在错误层面反复修改。",[32,33,34,35,36,37,38,39],"返回 HTML：错误页、登录页或反向代理页面通常以 \u003C 开头。","重复解析对象：response.json() 的结果或框架已反序列化的数据再次进入 JSON.parse。","单引号或未加引号的键：JavaScript 对象字面量写法不等于 JSON。","尾随逗号或注释：JSON 不接受对象/数组末尾逗号，也不支持 // 与 /* */ 注释。","字符串中出现未转义的换行、Tab、双引号或反斜杠。","非法数字：NaN、Infinity、01、1.、十六进制字面量都不是标准 JSON 数字。","空响应或传输被截断：常见表现为 unexpected end of JSON input。","BOM、多个根值或根值后还有额外字符，例如 {}{} 或 {}debug。",{"heading":41,"paragraphs":42,"code":45},"三、接口请求先检查状态码、Content-Type 和原始正文",[43,44],"直接调用 response.json() 会把网络层和解析层错误叠在一起。排查阶段可以先读取 text，记录状态码与 Content-Type，再决定是否解析。注意 Response 的 body 只能消费一次；下面的函数已经统一在 text 上处理。","不能只相信 Content-Type，因为配置错误的服务也可能把 HTML 标成 application/json；最终仍要检查正文。","async function fetchJson\u003CT>(url: string): Promise\u003CT> {\n  const response = await fetch(url, {\n    headers: { Accept: 'application/json' },\n  });\n\n  const contentType = response.headers.get('content-type') ?? '';\n  const text = await response.text();\n\n  if (!response.ok) {\n    throw new Error(`HTTP ${response.status}: ${text.slice(0, 200)}`);\n  }\n\n  if (!contentType.includes('application/json')) {\n    throw new Error(`Expected JSON, got ${contentType || 'unknown type'}`);\n  }\n\n  if (text.trim() === '') {\n    throw new Error('Expected JSON, got an empty response body');\n  }\n\n  try {\n    return JSON.parse(text) as T;\n  } catch (error) {\n    throw new Error(`Invalid JSON: ${(error as Error).message}`);\n  }\n}",{"heading":47,"paragraphs":48,"code":50},"四、最常见的语法差异：JSON 不是 JavaScript 对象字面量",[49],"下面几种文本在 JavaScript 源码里可能看起来熟悉，但都不能直接作为标准 JSON 解析。修复时应修改数据生产端，而不是在客户端用大范围替换猜测作者意图。","const invalidSamples = [\n  \"{'name':'Tiny'}\",          // 单引号\n  '{name:\"Tiny\"}',            // 键名未使用双引号\n  '{\"name\":\"Tiny\",}',       // 尾随逗号\n  '{\"enabled\":true // ok\\n}', // 注释\n  '{\"value\":NaN}',            // 非法数字\n  '{\"value\":01}',             // 前导零\n];\n\nfor (const sample of invalidSamples) {\n  try {\n    JSON.parse(sample);\n  } catch (error) {\n    console.log(sample, (error as Error).message);\n  }\n}",{"heading":52,"paragraphs":53,"code":56},"五、用错误位置截取上下文，而不是打印整份文件",[54,55],"V8 常在错误消息中提供 position，Firefox 可能提供 line 和 column，其他运行时的格式也可能不同。诊断函数应把位置解析视为“可选增强”，不能假设所有错误都包含同一种字段。","截取前后字符并显示 JSON.stringify 后的片段，可以让换行、Tab 和不可见字符显形。","function diagnoseJson(text: string): {\n  ok: true;\n  value: unknown;\n} | {\n  ok: false;\n  message: string;\n  position?: number;\n  context?: string;\n} {\n  try {\n    return { ok: true, value: JSON.parse(text) };\n  } catch (error) {\n    const message = (error as Error).message;\n    const match = message.match(/position\\s+(\\d+)/i);\n    const position = match ? Number(match[1]) : undefined;\n\n    if (position === undefined) {\n      return { ok: false, message };\n    }\n\n    const start = Math.max(0, position - 30);\n    const end = Math.min(text.length, position + 31);\n    return {\n      ok: false,\n      message,\n      position,\n      context: JSON.stringify(text.slice(start, end)),\n    };\n  }\n}",{"heading":58,"paragraphs":59,"code":62},"六、字符串转义错误必须在生成阶段修复",[60,61],"用户输入、Windows 路径和多行文本最容易在手工拼接 JSON 时破坏语法。正确做法是先构造 JavaScript 对象，再交给 JSON.stringify；不要自己拼双引号、反斜杠和换行。","如果字段本身保存另一段 JSON 字符串，需要按层 JSON.stringify 和 JSON.parse，具体方法见 /articles/nested-json-escaping。","const userInput = '第一行\\n第二行 \"quoted\" C:\\\\temp';\n\n// 错误：手工拼接很容易漏掉转义\n// const text = '{\"message\":\"' + userInput + '\"}';\n\nconst text = JSON.stringify({ message: userInput });\nconst parsed = JSON.parse(text) as { message: string };\n\nconsole.assert(parsed.message === userInput);",{"heading":64,"paragraphs":65,"bullets":68},"七、不要把“自动修复”当作默认策略",[66,67],"删除所有反斜杠、把单引号全换成双引号、用正则移除注释，都可能改变字符串字段中的真实数据。只有在输入格式被明确规定为 JSON5、JSONC 等非标准格式时，才应使用对应解析器，并在边界处转换成标准 JSON。","对于第三方数据，优先拒绝并返回清晰错误；对于自己控制的数据，修复序列化端并增加覆盖具体坏样本的测试。",[69,70,71,72],"204 No Content 不应强制解析 JSON。","一个响应只能有一个 JSON 根值，多个对象不能直接连续拼接。","空字符串不是合法 JSON；表示空值应使用 null。","BOM 或前导不可见字符应先确认来源，再在文件读取边界做定向处理。",{"heading":74,"paragraphs":75},"八、验证流程、FAQ 与结论",[76,77,78,79,80],"验证顺序：保存原始文本；确认类型和来源；检查 HTTP 状态与 Content-Type；用最小样本复现；根据错误位置查看上下文；修复生产端；最后用原坏样本回归。可使用首页 JSON Formatter（/）快速验证文本，但不要把敏感数据复制到不受信任的第三方页面。","问：为什么 Chrome 与 Firefox 的报错文字不同？答：ECMAScript 规定抛出 SyntaxError，但不要求各引擎使用完全相同的消息。","问：Unexpected token \u003C 是否一定是 404？答：不一定，只能说明开头很可能是 HTML；还可能是登录页、WAF 页面或服务端错误模板。","问：try...catch 能修复 JSON 吗？答：不能，它只能阻止异常中断流程，并提供降级或错误提示。","结论：定位 Unexpected token 的核心不是背错误文案，而是保留原始输入并判断错误发生在网络层、调用层还是 JSON 语法层。",{"title":82,"description":83,"intro":84,"sections":85},"JSON.parse Unexpected Token: 8 Causes and a Reliable Debugging Flow","Debug JSON.parse syntax failures by inspecting the raw response, input type, error position, and JSON grammar. Covers HTML responses, double parsing, trailing commas, escaping, invalid numbers, and truncated payloads.","When JSON.parse throws “Unexpected token”, “unexpected character”, or “bad parsing”, the exact message is engine-dependent. The durable questions are: what input reached the parser, where did parsing stop, and what token was expected there? This guide uses reproducible input categories instead of unsafe, one-size-fits-all repair regexes.",[86,91,104,109,114,119,124,133],{"heading":87,"paragraphs":88,"code":90},"1. Confirm that the input is JSON text",[89],"JSON.parse coerces non-string inputs. Passing an object that was already parsed can become “[object Object]” and fail near the beginning. If fetch response.json() or a framework already returned an object, do not parse it again.","function parseJsonText(input: unknown): unknown {\n  if (typeof input !== 'string') {\n    throw new TypeError(`Expected JSON text, received ${typeof input}`);\n  }\n  return JSON.parse(input);\n}",{"heading":92,"paragraphs":93,"bullets":95},"2. Eight common causes, grouped by source",[94],"First decide whether the payload is not JSON at all or is JSON-shaped text with invalid syntax. That distinction prevents changes at the wrong layer.",[96,97,98,99,100,101,102,103],"HTML error, login, proxy, or WAF page returned instead of JSON.","An object is parsed a second time.","Single-quoted strings or unquoted property names.","Trailing commas or JavaScript-style comments.","Unescaped newlines, tabs, quotes, or backslashes inside strings.","Invalid numbers such as NaN, Infinity, 01, 1., or hexadecimal literals.","Empty or truncated response bodies.","A BOM, multiple root values, or extra non-whitespace text after the root value.",{"heading":105,"paragraphs":106,"code":108},"3. Inspect HTTP status, Content-Type, and raw body first",[107],"During diagnosis, read response.text() once and then validate it. Content-Type is useful but not conclusive because servers can mislabel HTML as JSON.","async function fetchJson\u003CT>(url: string): Promise\u003CT> {\n  const response = await fetch(url, {\n    headers: { Accept: 'application/json' },\n  });\n  const type = response.headers.get('content-type') ?? '';\n  const text = await response.text();\n\n  if (!response.ok) {\n    throw new Error(`HTTP ${response.status}: ${text.slice(0, 200)}`);\n  }\n  if (!type.includes('application/json')) {\n    throw new Error(`Expected JSON, got ${type || 'unknown type'}`);\n  }\n  if (text.trim() === '') {\n    throw new Error('Expected JSON, got an empty body');\n  }\n\n  return JSON.parse(text) as T;\n}",{"heading":110,"paragraphs":111,"code":113},"4. JSON is stricter than a JavaScript object literal",[112],"JSON requires double-quoted property names and strings, rejects trailing commas and comments, and has a limited number grammar. Fix the producer instead of guessing with broad text replacements.","const invalid = [\n  \"{'name':'Tiny'}\",\n  '{name:\"Tiny\"}',\n  '{\"name\":\"Tiny\",}',\n  '{\"enabled\":true // comment\\n}',\n  '{\"value\":NaN}',\n  '{\"value\":01}',\n];\n\nfor (const sample of invalid) {\n  try {\n    JSON.parse(sample);\n  } catch (error) {\n    console.log((error as Error).message);\n  }\n}",{"heading":115,"paragraphs":116,"code":118},"5. Extract context when an error position is available",[117],"V8 often reports a character position, while other engines may report line and column or a different message. Treat position parsing as an optional diagnostic enhancement and print escaped context so invisible characters become visible.","function diagnoseJson(text: string) {\n  try {\n    return { ok: true as const, value: JSON.parse(text) };\n  } catch (error) {\n    const message = (error as Error).message;\n    const match = message.match(/position\\s+(\\d+)/i);\n    const position = match ? Number(match[1]) : undefined;\n    if (position === undefined) return { ok: false as const, message };\n\n    const start = Math.max(0, position - 30);\n    const end = Math.min(text.length, position + 31);\n    return {\n      ok: false as const,\n      message,\n      position,\n      context: JSON.stringify(text.slice(start, end)),\n    };\n  }\n}",{"heading":120,"paragraphs":121,"code":123},"6. Generate JSON with JSON.stringify",[122],"Manual concatenation breaks as soon as user content contains quotes, backslashes, or line breaks. Build an object first and let JSON.stringify apply the required escaping. Embedded JSON strings need one stringify/parse operation per representation layer; see /articles/nested-json-escaping.","const input = 'line 1\\nline 2 \"quoted\" C:\\\\temp';\nconst text = JSON.stringify({ message: input });\nconst parsed = JSON.parse(text) as { message: string };\nconsole.assert(parsed.message === input);",{"heading":125,"paragraphs":126,"bullets":128},"7. Why generic auto-repair is unsafe",[127],"Removing all backslashes, replacing every single quote, or deleting comments with regex can alter legitimate string data. Use a JSON5 or JSONC parser only when that input format is explicitly part of the contract, then convert at the boundary.",[129,130,131,132],"Do not parse a 204 No Content response as JSON.","An empty string is not JSON; use null for an explicit empty value.","A response contains one root JSON value, not concatenated objects.","Handle BOMs or other prefixes only after confirming their source.",{"heading":134,"paragraphs":135},"8. Verification, FAQ, and conclusion",[136,137,138,139],"Preserve the raw payload, classify the source, reproduce with the smallest failing text, inspect context, fix the producer, and keep the original bad sample as a regression test. The homepage formatter at / can help validate non-sensitive text.","Why do browsers show different messages? The language requires a SyntaxError but does not standardize the full wording.","Does “Unexpected token \u003C” always mean a 404? No. It usually indicates HTML, which may be a login page, proxy response, or server error template.","try...catch contains the failure; it does not repair invalid JSON. The reliable fix is to identify whether the defect belongs to transport, call flow, or JSON generation.",{"name":141,"url":142},"Tiny's Tool Editorial Team","https://tinystool.org/about",{"name":144,"url":142},"Tiny's Tool Technical Review","2026-08-08",[147],{"title":148,"url":149,"publisher":150},"RFC 8259 — The JavaScript Object Notation (JSON) Data Interchange Format","https://www.rfc-editor.org/rfc/rfc8259","RFC Editor",false,[153,168,184],{"slug":154,"noIndex":151,"category":155,"date":7,"updatedDate":7,"reviewedDate":145,"readingMinutes":156,"tags":157,"relatedTool":15,"cn":162,"en":165},"json-number-precision","踩坑避坑",8,[10,158,159,160,161],"JavaScript","Number","BigInt","精度丢失",{"title":163,"description":164},"JSON 大整数精度丢失：Number 安全范围与解决方案","说明 JSON 大整数进入 JavaScript 后为什么会被舍入，演示 Number.MAX_SAFE_INTEGER 边界，并给出字符串契约、BigInt 转换和序列化的可验证方案。",{"title":166,"description":167},"Large Integers in JSON: JavaScript Precision Limits and Fixes","Learn why valid JSON integers can be rounded when parsed into JavaScript Number values, and apply string contracts, BigInt conversion, and safe serialization without losing digits.",{"slug":169,"noIndex":170,"category":6,"date":7,"updatedDate":7,"reviewedDate":145,"readingMinutes":171,"tags":172,"relatedTool":177,"cn":178,"en":181},"json-diff-unordered-keys",true,6,[173,174,175,176],"JSON Diff","Object Keys","Canonicalization","Comparison","/tools/json/json-diff",{"title":179,"description":180},"JSON 对象键顺序不同为何 Diff：结构化比较方法","解释 JSON 文本比较与结构化比较的区别，演示如何递归规范化对象键且保留数组顺序，并列出数字、重复键和数组语义等边界条件。",{"title":182,"description":183},"Why JSON Key Order Changes a Diff: Structural Comparison","Learn the difference between JSON text and structural comparison, with a recursive object-key canonicalizer that deliberately preserves array order and documents key boundaries.",{"slug":185,"noIndex":170,"category":6,"date":7,"updatedDate":7,"reviewedDate":145,"readingMinutes":186,"tags":187,"relatedTool":193,"cn":194,"en":197},"json-to-sql-type-inference",5,[188,189,190,191,192],"JSON to SQL","MySQL","PostgreSQL","Type Inference","Schema","/tools/json/json-to-sql",{"title":195,"description":196},"JSON 转 SQL 类型推断：MySQL 与 PostgreSQL 的边界","分开说明 MySQL 与 PostgreSQL 对 JSON、数字、字符串、数组、对象和 NULL 的类型建议，解释为什么样本推断不能替代明确的数据库 schema。",{"title":198,"description":199},"JSON-to-SQL Type Inference: MySQL and PostgreSQL Boundaries","Generate conservative MySQL and PostgreSQL candidates for JSON, numbers, strings, arrays, objects, and NULL while explaining why samples cannot replace an explicit schema.",1786725508067]