前端 JSON 安全解析实战:以 try/catch 与结构校验守护运行时(Front-End-Checklist json-safety 规则深度解读) 📅 发布时间:2026/9/19 3:59:41 👁 浏览次数: 前端 JSON 安全解析实战以 try/catch 与结构校验守护运行时Front-End-Checklist json-safety 规则深度解读【免费下载链接】Front-End-Checklist The essential checklist for modern web development, for humans and AI agents项目地址: https://gitcode.com/gh_mirrors/fr/Front-End-Checklist本篇技术指南以 Front-End-Checklist 仓库中的json-safety规则skills/json-safety/SKILL.md 及其完整实现 references/rule.md为骨架结合仓库中真实源码佐证系统讲解JSON.parse()的抛错机制、安全解析范式、数据结构校验、schema 校验库Zod的应用时机以及JSON.stringify()的序列化边界。读完本文你将掌握一套可落地的「解析 校验 兜底」模式知道如何在 React 客户端组件、localStorage 读写、API 响应解析等真实场景中避免未捕获异常导致的白屏与崩溃。规则定位一条属于 JavaScript 质量域的检查项在 Front-End-Checklist 的内容体系中json-safety是一条javascript/quality子类目下的规则元数据定义见 packages/content/rules/en/javascript/json-safety.mdx优先级medium中难度beginner入门级适合所有前端开发者预估耗时10 分钟一句话概括永远用 try/catch 包裹JSON.parse()并在使用解析结果前校验其结构因为无效 JSON 或意外的数据结构会引发运行时错误。该规则也被封装为可供 AI Agent 直接调用的技能SKILL见 skills/json-safety/SKILL.md其 frontmatter 明确声明了使用场景审查脚本、客户端组件、打包产物或与「安全解析 JSON」相关的运行时行为时使用并要求同时检查源码与浏览器执行路径确保修复落在真正的瓶颈或 bug 上。为什么 JSON.parse 会成为运行时崩溃的源头JSON.parse()在输入非法时抛出SyntaxError如果未捕获会直接中断当前执行栈导致应用崩溃。而它的输入来源恰恰是最不可控的几类数据API 响应后端字段缺失、接口降级、网关返回 HTML 错误页都可能让响应体不是合法 JSONlocalStorage / sessionStorage 取值缓存被旧版本写入、用户手动篡改、存储被部分截断读出来的是坏字符串用户提供的数据导入文件、粘贴的配置文本等。更隐蔽的问题是「解析成功但形状不对」。JSON.parse()只保证语法合法不保证data.user.profile.name一定存在。若解析后立刻深链访问属性任何一层缺失都会抛出TypeError——而这个错误发生在业务逻辑深处定位成本远高于在解析入口处就地拦截。正如规则文档所强调的安全解析在解析点捕获错误而不是等到应用逻辑深处因属性缺失而意外报错。最小安全解析范式// ❌ 输入非法时抛出未捕获的 SyntaxError const data JSON.parse(input) // ✅ 始终用 try/catch 包裹 function safeParse(json, fallback null) { try { return JSON.parse(json) } catch { return fallback } } const config safeParse(localStorage.getItem(config), {})关键点在于catch分支返回一个与预期类型一致的兜底值如空对象、空数组这样调用方无需到处判断「是否抛异常」只用处理「兜底值」这一种形态即可。解析成功不等于安全必须校验结构仅仅把JSON.parse包进 try/catch 只解决了一半问题。另一半是结构校验——在访问嵌套属性之前确认数据形状符合预期// 只是解析还不够——形状可能是错的 const raw JSON.parse(apiResponse) const name raw.user.profile.name // 任何一层属性缺失都会 TypeError // ✅ 使用前先校验 function parseUserResponse(json) { try { const data JSON.parse(json) if (typeof data?.user?.profile?.name ! string) { throw new Error(Invalid user response shape) } return data } catch (error) { console.error(Failed to parse user response:, error) return null } }这里示范了「校验 异常合一」的处理策略结构不合法时主动throw与JSON.parse的语法错误一起被同一个catch捕获从而让函数只有一个失败出口调用方逻辑保持简单。仓库实战useUserChecklists 的类型守卫式校验这种「解析 形状校验」模式在仓库前端代码中有非常完整的实现。在 apps/web/hooks/use-user-checklists.ts 中对服务端返回的 checklist 负载做了一层层运行时校验function isRecord(value: unknown): value is Recordstring, unknown { return typeof value object value ! null } function isStringArray(value: unknown): value is string[] { return Array.isArray(value) value.every(item typeof item string) } function isUserChecklist(value: unknown): value is UserChecklist { if (!isRecord(value)) return false const description value.description const color value.color const framework value.framework return ( typeof value.id string typeof value.name string isStringArray(value.ruleIds) typeof value.createdAt string typeof value.updatedAt string (description undefined || typeof description string) (framework undefined || isChecklistFramework(framework)) (color undefined || typeof color string) ) }parseUserChecklist在守卫不通过时抛出new Error(Invalid checklist payload)把错误拦截在数据进入业务状态之前fetchChecklistsFromApi读取fetch(/api/checklists)的 JSON 后还额外做了一次Array.isArray(data) ? data.filter(isUserChecklist) : []的防御——对数组中每一个元素做守卫过滤无法通过校验的条目直接被剔除而不是让整页崩溃。同一文件中的importChecklist则演示了「用户提供的数据」场景用户导入的 JSON 文件先经JSON.parse放在 try/catch 中再校验顶层形状name必须是 string、ruleIds必须是字符串数组任何一步失败都静默返回nullconst importChecklist useCallback((jsonData: string) { try { const imported JSON.parse(jsonData) if (!isRecord(imported)) throw new Error(Invalid format) if (typeof imported.name ! string || !isStringArray(imported.ruleIds)) { throw new Error(Invalid format) } // ... void createChecklist(...).catch(() {}) return null } catch { return null } }, [createChecklist, isSignedIn])这组代码是 json-safety 规则「解析 校验」两步走的教科书级落地外部输入API 或用户文件一律视为不可信先解析、再守卫、最后才进入业务逻辑。复杂结构用类型安全解析器Zod 与 safeParse当数据是嵌套复杂对象用户信息、订单、配置手写typeof守卫会迅速膨胀且易漏。此时应引入类型安全的运行时校验库。规则文档推荐的方案是Zod以及轻量替代Valibot两者在 json-safety.mdx 的tools元数据中被显式列出import { z } from zod const UserSchema z.object({ id: z.number(), name: z.string(), email: z.string().email(), role: z.enum([admin, user, moderator]) }) function parseUser(json) { try { const raw JSON.parse(json) return UserSchema.parse(raw) // 形状错误时抛出 ZodError } catch (error) { console.error(User parsing failed:, error) return null } } // 或者用 safeParse —— 返回 { success, data, error } 判别联合 const result UserSchema.safeParse(raw) if (result.success) { processUser(result.data) // 此处 data 是完全类型化的 }两条路径各有取舍parse()抛出异常适合与try/catch的既有错误处理整合safeParse()返回判别联合discriminated union调用方通过result.success分支即可拿到类型收窄后的result.data无需 try/catch。仓库实战schemas 包的系统级 schema 体系Front-End-Checklist 仓库已经把 Zod 用于全栈数据边界集中在 packages/schemas/src/index.ts。该文件为规则、用户进度、偏好、导出数据、导入数据、分析事件、功能开关等定义了完整的 schema并通过safeParse统一封装成可复用的校验函数export const userProgressSchema z.object({ ruleId: z.string().min(1), completed: z.boolean(), completedAt: z.date().optional(), notes: z.string().max(1000).optional() }) export const schemaValidators { validateUserProgress(data: unknown) { return userProgressSchema.safeParse(data) }, validateImportData(data: unknown) { return importDataSchema.safeParse(data) } // ... } export const validateUserProgress schemaValidators.validateUserProgress随后这些validateXxx函数被存储层实际消费packages/storage/src/index.ts 的saveProgress在写入前逐条过滤progress.filter(item validateUserProgress(item).success)savePreferences在持久化前用validateUserPreferences(preferences)校验失败直接throw new Error(Invalid preferences)。这就是「外部来源数据必须经过类型安全解析器」在真实项目中的标准用法——写前校验拒绝脏数据入库。JSON.stringify 的序列化边界与安全序列化安全 JSON 处理不仅关于解析序列化同样有坑。规则文档明确指出JSON.stringify()对无法序列化的值会返回undefined而非字符串且会静默丢弃若干类型JSON.stringify(undefined) // undefined不是字符串 JSON.stringify({ a: undefined }) // {} —— 属性被丢弃 JSON.stringify({ fn: () {} }) // {} —— 函数被丢弃 JSON.stringify(new Date()) // 2024-01-15T... —— 序列化为字符串 JSON.stringify(new Map([[1, 2]])) // {} —— Map 无法序列化 // 安全序列化 function safeStringify(value, fallback {}) { try { const result JSON.stringify(value) return result ?? fallback } catch { return fallback } }两个必须记住的事实JSON.stringify(undefined)的返回值不是字符串而是undefined——若把它直接localStorage.setItem(key, ...)会被隐式转为字符串undefined下次JSON.parse直接抛错JSON.stringify遇到循环引用circular reference或BigInt会抛出TypeError同样需要 try/catch 兜底。此外还有隐性风险序列化前的对象如果含undefined/函数/Map 等值数据会静默丢失造成「存进去 10 个字段、读出来 7 个」的难查 bug。仓库实战存储层的写入与读取双向防护packages/storage/src/storage-helpers.ts 展示了与规则完全一致的读写双防护实践。写入侧collectProjectLocalStorage遍历fec_前缀键并逐个JSON.parse但每一个 parse 都在 try/catch 内遇到损坏值跳过而非中断整个导出export function collectProjectLocalStorage(): Recordstring, unknown { const exported: Recordstring, unknown {} for (let index 0; index window.localStorage.length; index 1) { const key window.localStorage.key(index) if (!key?.startsWith(fec_)) continue try { const value window.localStorage.getItem(key) if (value) { exported[key] JSON.parse(value) } } catch { // 导出时跳过损坏值。 } } return exported }读取侧packages/storage/src/index.ts 的getLocal不仅把JSON.parse包进 try/catch还在解析后校验带上的元数据——过期时间与缓存版本解析出的StorageItem若expiresAt已过期或version与当前CACHE.VERSION不一致则直接删除并返回null。这条「解析后校验」逻辑与 json-safety 规则的「先解析、再校验、后使用」完全同构const parsed: StorageItem JSON.parse(item) if (parsed.expiresAt new Date(parsed.expiresAt) new Date()) { this.removeLocal(key) return null } if (parsed.version ! CACHE.VERSION) { this.removeLocal(key) return null }解析 API 响应的完整姿势fetch().then(r r.json())是另一个高频事故点。规则文档强调response.json()内部已经执行了JSON.parse并自带解析错误处理但它会在非 JSON 内容类型上抛错例如网关返回的 502 HTML 页面。因此规范的 API 解析应先检查response.ok与Content-Typeasync function fetchData(url) { const response await fetch(url) if (!response.ok) { throw new Error(HTTP error: ${response.status}) } // response.json() 内部已做 JSON.parse try/catch // 但它对非 JSON 内容类型会抛错 const contentType response.headers.get(content-type) if (!contentType?.includes(application/json)) { throw new Error(Response is not JSON) } return response.json() }从源码结构看Front-End-Checklist 的存储层把这类「HTTP JSON 校验」的失败处理做了统一收敛reportStorageError见 storage-helpers.ts负责记录错误、getLocal返回null兜底、fetchChecklistsFromApi对!res.ok直接返回空数组。这一设计印证了规则的核心思想——把错误收在数据边界而不是让异常沿调用链传播到 UI。相关规则json-safety 的生态位json-safety并非孤立存在。根据 json-safety.mdx 的relatedRules元数据它与以下规则形成完整的「数据边界安全」检查族web-storagelocalStorage 的值永远是字符串取用必须JSON.parse——而它可能失败是 json-safety 最典型的触发场景avoid-evaleval()曾被用来解析 JSONJSON.parse是它的安全替代品error-handling失败的 API 请求常在 JSON 解析阶段抛错而非fetch()本身no-unchecked-indexed-access同属javascript/quality区域常在评审中一起审查。如何在代码评审中检查与验证规则的 SKILL 文件skills/json-safety/SKILL.md给出了面向代码审查的四步操作法Check找出本文件内所有JSON.parse()调用逐个确认是否被 try/catch 包裹、结果是否在使用前经过校验Fix为所有JSON.parse()补充 try/catch并添加形状校验以抵御意外数据结构Explain解释JSON.parse为何会抛错、安全解析长什么样、何时该用 schema 校验库Code Review审查脚本、客户端组件与浏览器执行路径中与本规则相关的部分精确标记违规的 import、事件处理器、运行时副作用或阻塞操作并说明如何在浏览器中验证修复效果。自动化检查MCP review_code 工具值得强调的是该规则已经落地为可自动执行的静态启发式检查。仓库的 MCP 服务提供了review_code工具packages/mcp/src/tools/review-code.ts其中包含专门的 json-safety 检测逻辑// json-safety —— JSON.parse 在非法输入时抛错必须包在 try-catch 中 if (slug.includes(json-safety)) { const jsonParseCalls (code.match(/JSON\.parse\s*\(/g) || []).length if (jsonParseCalls 0 !lowerCode.includes(try) !lowerCode.includes(catch)) { return { hasIssue: true, issue: Found ${jsonParseCalls} JSON.parse() call(s) without try-catch — malformed JSON will throw an uncaught error } } }其配套单元测试packages/mcp/tests/unit/review-code-detection.test.ts用最小复现验证了检测能力it(detects JSON.parse without try-catch, () { const js const data JSON.parse(userInput); const rules rulesDetectedIn(js, [javascript]) expect(rules).toContain(json-safety) })也就是说const data JSON.parse(userInput)这种裸调用可以被自动识别并标记为违规——人工评审与自动化检查可以互为补充。验证清单规则文档 references/rule.md 给出了明确的验证要求可概括为自动化检查代码修改后务必在浏览器中验证行为而非只看静态分析结果若改动影响加载或执行顺序检查 DevTools 的 Network 或 Performance 面板测试主用户流程与改动脚本路径触发的一个边界用例手动检查确认功能在延迟加载、懒加载或失败场景下行为依然正确——即「坏数据/坏网络」环境下页面不白屏、有兜底 UI。核心结论速览要点说明解析入口JSON.parse()必须包 try/catch兜底值类型要与预期一致结构校验解析成功 ≠ 安全访问嵌套属性前先守卫typeof / 自定义守卫 / Zod复杂数据外部来源数据优先使用 ZodsafeParse()返回{ success, data, error }判别联合序列化JSON.stringify对 undefined/函数/Map 静默丢弃对循环引用抛错用safeStringify 兜底API 响应先查response.ok与Content-Type再response.json()存储层localStorage 读取后校验 TTL 与版本元数据损坏值直接跳过或删除自动检查仓库 MCPreview_code工具可检测无 try/catch 的裸JSON.parse()这条规则的落地要点可以浓缩为一句话所有跨越数据边界网络、存储、用户输入的 JSON都要经历「安全解析 → 形状校验 → 兜底处理」三步把异常拦截在入口处而不是让它在业务逻辑深处引爆。【免费下载链接】Front-End-Checklist The essential checklist for modern web development, for humans and AI agents项目地址: https://gitcode.com/gh_mirrors/fr/Front-End-Checklist创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考