前端 JSON 安全解析实战指南:try/catch 与 Schema 校验(Front-End-Checklist 之 json-safety 规则深度解析)

发布时间:2026/9/19 13:14:39
前端 JSON 安全解析实战指南:try/catch 与 Schema 校验(Front-End-Checklist 之 json-safety 规则深度解析) 前端 JSON 安全解析实战指南try/catch 与 Schema 校验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-ChecklistJSON.parse()是前端最容易引发未捕获异常的内置 API 之一——当数据来自 API 响应、localStorage 缓存或用户输入时任何一段畸形 JSON 都可能让整页应用崩溃。本指南以开源仓库 Front-End-Checklist 的 json-safety 规则 为核心系统讲解解析前兜底、解析后验形、序列化防坑、响应预检四层防护体系并结合仓库内真实的存储层、Schema 校验层与 React Hook 实现带你掌握一套可复制、可测试的健壮 JSON 处理方案。规则速览这是什么、针对什么问题在 Front-End-Checklist 的规则体系中json-safety 是一条定位为javascript / quality类别的规则元数据见 规则 MDX 源文件 头部 frontmatter如下优先级medium难度beginner预估耗时10 分钟核心主张永远用try/catch包裹JSON.parse()并在使用前校验解析结果的结构——无效 JSON 或意外的数据结构会在运行时抛错。技能定义文件 skills/json-safety/SKILL.md 给出了四条 Quick Reference可视为本规则的浓缩版JSON.parse()遇到无效输入会抛出SyntaxError——必须使用try/catch在访问解析结果的属性之前先校验其数据形状shapeJSON.stringify()对无法序列化的值可能返回undefined对来自外部源的数据使用类型安全解析器Zod、Valibot。规则的完整讲解、代码示例与框架相关指引存放在 references/rule.md下文所有核心代码均继承自该文档并结合仓库源码逐层展开。为什么 JSON.parse 必须被兜底SyntaxError 的真相JSON.parse()的输入必须是一个合法的 JSON 字符串。只要输入不合规截断的网络响应、被污染的 localStorage 值、用户手改的配置项它就会抛出SyntaxError如果这个异常未被捕获将直接中断当前脚本执行造成整页功能失效。关键认知在于错误往往不在解析现场爆发而在你访问属性时爆发。文档明确指出API responses, localStorage values, and user-provided data can all be malformed. Safe parsing with validation catches these errors at the point of parsing, not deep in your application logic when a missing property causes an unexpected error.即安全解析 结构校验能把错误拦截在解析点而不是让应用逻辑深处因为访问不存在的属性而抛出难以定位的TypeError。最基础的兜底模式文档给出的最小可运行方案// ❌ Throws SyntaxError if input is invalid const data JSON.parse(input) // ✅ Always wrap in try/catch function safeParse(json, fallback null) { try { return JSON.parse(json) } catch { return fallback } } const config safeParse(localStorage.getItem(config), {})fallback参数的默认值是null但实际使用时应针对场景传入更安全的默认值——例如读取用户配置时传{}保证后续的config.xxx访问不会在null上崩溃。这是文档推荐的最小防护也是后续所有进阶模式的基石。仓库实证真实项目里的 safeParse 长什么样这条规则不是纸上谈兵。在仓库的浏览器存储封装层 packages/storage/src/index.ts 中getLocal方法完整实践了try/catch 校验模式getLocalT any(key: string): T | null { if (typeof window undefined) { return null } if (this.memoryCache.has(key)) { return this.memoryCache.get(key) } try { const item localStorage.getItem(key) if (!item) { return null } 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 } this.memoryCache.set(key, parsed.value) return parsed.value } catch (error) { reportStorageError(LocalStorage read failed:, error) return null } }这段代码有三点值得学习解析即兜底JSON.parse(item)被包在try/catch里任何畸形缓存都返回null而不是抛出异常解析后验形解析出的StorageItem立即做expiresAt过期时间与version版本号双重校验不合法就直接移除该 key 并返回null——这正是validate the parsed structure before use的工程化落地降级路径解析失败时通过reportStorageError见 storage-helpers.ts记录错误测试环境下自动抑制生产环境输出到控制台。与之对称的写入侧setLocalindex.ts同样把JSON.stringify(item)包进try/catch写入失败如超出配额时回退到内存缓存保证应用不因存储异常而崩溃。第二层防护解析成功 ≠ 形状正确仅仅try/catch远远不够——JSON 语法合法不代表结构符合预期。文档中的经典反例// Just parsing isnt enough — the shape might be wrong const raw JSON.parse(apiResponse) const name raw.user.profile.name // TypeError if any property is missing!只要user或profile任意一层缺失这条链式访问就会抛出TypeError。正确姿势是先校验形状再使用// ✅ Validate before use 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 } }这里使用了可选链data?.user?.profile?.name做逐层存在性检查并用typeof ! string校验字段类型形状不合法时主动抛出错误统一被catch收口。仓库实证API 返回的数组校验在 apps/web/hooks/use-progress.ts 中前端获取用户进度数据时同样做了解析 结构校验双重防护async function fetchProgressFromApi(): PromiseUserProgress[] { const res await fetch(/api/progress) if (!res.ok) return [] const data await res.json() return Array.isArray(data) ? parseProgressFromApi(data) : [] }注意Array.isArray(data)这个校验即使res.json()成功解析也不能假设返回的一定是数组——非数组数据被直接安全地替换为空数组随后parseProgressFromApi再把每条记录中的completedAt字符串规范化为Date实例。这与文档validate before use的原则完全一致任何外部数据在进入业务逻辑前都必须先通过结构闸门。第三层防护用 Zod / Valibot 做运行时 Schema 校验手写typeof检查在小场景够用但字段一多就难以维护。文档推荐使用类型安全解析器Zod、Valibot为外部数据定义 Schemaimport { 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) // Throws ZodError if shape is wrong } catch (error) { console.error(User parsing failed:, error) return null } } // Or use safeParse which returns { success, data, error } const result UserSchema.safeParse(raw) if (result.success) { processUser(result.data) // Fully typed! }两种用法的区别要分清UserSchema.parse(raw)校验失败时抛出ZodError适合配合try/catch收口UserSchema.safeParse(raw)不抛异常返回{ success, data, error }判别联合通过result.success分支判断且result.data具有完整类型推导。仓库实证Zod 在数据持久层的大规模落地这个仓库本身就是 Zod 的深度使用者。在 packages/schemas/src/index.ts 中用户进度数据被定义为export const userProgressSchema z.object({ ruleId: z.string().min(1), completed: z.boolean(), completedAt: z.date().optional(), notes: z.string().max(1000).optional() })而 storage/index.ts 的saveProgress在写入前先用safeParse过滤非法记录async saveProgress(progress: UserProgress[]): Promisevoid { const validProgress progress.filter( progressItem validateUserProgress(progressItem).success ) await this.setIndexedDB(progress, all, validProgress) this.setLocal(STORAGE_KEYS.USER_PROGRESS, validProgress, CACHE.TTL.USER_DATA) }这构成了文档所倡导模式的完整闭环外部/持久化数据localStorage、IndexedDB→ JSON.parse 兜底 → Zod safeParse 结构校验 → 只有合法数据进入业务逻辑。仓库还基于同样的思路定义了userPreferencesSchema、importDataSchema、exportDataSchema等一整套 Schema见 schemas/index.ts并通过schemaValidators统一暴露safeParse风格的校验函数。反向防护JSON.stringify 的边界情况解析之外序列化同样暗藏陷阱。文档用一组精确的示例说明了JSON.stringify的静默丢弃行为// Some values become undefined in JSON.stringify: JSON.stringify(undefined) // undefined (not a string!) JSON.stringify({ a: undefined }) // {} — property dropped! JSON.stringify({ fn: () {} }) // {} — functions dropped! JSON.stringify(new Date()) // 2024-01-15T... — serialized as string JSON.stringify(new Map([[1, 2]])) // {} — Maps dont serialize!注意第一条最反直觉JSON.stringify(undefined)返回的不是字符串undefined而是原始值undefined——如果你把这个结果直接拼进字符串或当作字符串处理就会踩坑。此外对象中的undefined属性、函数属性会被静默丢弃Map/Set会被序列化成空对象Date则被转成 ISO 字符串。因此序列化侧同样需要兜底// Safe serialization function safeStringify(value, fallback {}) { try { const result JSON.stringify(value) return result ?? fallback } catch { return fallback } }result ?? fallback处理了返回undefined的场景??只在值为null/undefined时取后备值try/catch则兜住循环引用导致的TypeError: Converting circular structure to JSON。仓库实证序列化防护的应用点仓库中JSON.stringify的使用同样遵循明确知晓返回值语义的原则在 apps/web/hooks/use-progress.ts 的exportProgress中用JSON.stringify(data, null, 2)生成带缩进的导出 JSON并通过BlobURL.createObjectURL触发下载在 apps/web/lib/mcp-cache.ts 中generateCacheKey用JSON.stringify(body)把请求体序列化后再做 djb2 哈希生成缓存键——这正是stringify 前先确认数据可序列化的典型场景。实战安全解析 API 响应把以上所有原则组合起来就是文档给出的完整fetchData示例async function fetchData(url) { const response await fetch(url) if (!response.ok) { throw new Error(HTTP error: ${response.status}) } // response.json() already does JSON.parse() try/catch // But it throws on non-JSON content types const contentType response.headers.get(content-type) if (!contentType?.includes(application/json)) { throw new Error(Response is not JSON) } return response.json() }要点拆解先查 HTTP 状态!response.ok4xx/5xx直接抛出带状态码的错误再验 Content-Typeresponse.json()内部就是JSON.parse遇到非 JSON 响应HTML 错误页、代理网关页会抛出解析错误所以先通过content-type头预检避免把 HTML 当 JSON 解析返回值交给调用方解析成功后调用方应继续套用本指南的形状校验或 Zod Schema——文档中关联规则 error-handling 明确指出Failed API responses often fail during JSON parsing, not fetch()即许多接口错误其实发生在解析环节而非网络请求环节。关联规则与哪些检查项协同审查规则 MDX 源文件 的relatedRules字段列出了四条经常与本规则一起审查的相邻规则关联规则协同理由web-storagelocalStorage 的值永远是字符串必须经JSON.parse读取——而它可能解析失败avoid-evaleval()曾被用来解析 JSONJSON.parse是其安全替代品error-handling失败的 API 响应往往在 JSON 解析环节失败而非fetch()本身no-unchecked-indexed-access两者同属javascript/quality领域常一起审查其中与 web-storage 的关联最为直接本文开头的safeParse(localStorage.getItem(config), {})正是两条规则的交叉地带——读取 localStorage 必然涉及字符串到对象的反序列化任何旧的、被污染的缓存值都可能让解析失败。标准依据以什么为规范基准规则文档的 Standards 部分明确了两份规范基准作为行为标准而非本地小示例MDN: JavaScript Guide—— 作为该 JavaScript 模式在生产环境应如何表现的标准参考web.dev: Learn JavaScript—— 作为实现层面的行为基准。这两份资料共同界定了JSON.parse/JSON.stringify的规范语义异常类型、返回类型、序列化规则审查代码时以它们为准绳而不是仅凭本地小例子推断行为。验证清单如何确认改动真的安全规则文档提供了分层的验证策略可用于代码评审与自查自动化检查代码改动后在浏览器中验证行为而非仅依赖静态分析当规则影响加载或执行顺序时检查 DevTools 的Network或Performance面板测试主用户流程 一条由改动脚本路径触发的边缘用例如注入畸形 JSON。手动检查确认功能在延迟加载、懒加载或失败的情况下仍能正确表现——例如 localStorage 中存有旧版本数据结构、或 API 返回 500 错误页时页面不应白屏。仓库对应的单元测试 packages/storage/src/tests/storage.test.ts 正是这一策略的实践它显式向 localStorage 写入JSON.stringify({ ok: true })的缓存项再断言读取、清理、跨应用 key 隔离等行为覆盖了缓存值合法但结构不符预期的典型场景。规则在 AI 审查工作流中的落地方式作为一条面向人类与 AI Agent的检查规则json-safety 在仓库中被结构化成了可被工具消费的形态。除了本文讲解的 规则参考文档SKILL.md 还给出了四个标准审查动作Check找出文件内所有JSON.parse()调用逐一检查是否被try/catch包裹、结果是否在使用前经过校验Fix为所有JSON.parse()调用补上try/catch错误处理并添加形状校验以防御意外数据结构Explain解释为什么JSON.parse会抛出异常、安全的 JSON 解析长什么样、何时该引入 Schema 校验库Code Review审查脚本、客户端组件与浏览器执行路径标记违反规则的精确 import、事件处理器、运行时副作用或阻塞操作并说明应在浏览器中如何验证改动。这套动作与规则元数据priority: medium、difficulty: beginner、estimatedTime: 10一起让该规则可以被 MCP 审查工具、CI 校验脚本见仓库 scripts/validation 下的检查体系和规则目录 docs/generated/rules-catalog.md 统一索引与调用。小结四层 JSON 防护体系综合文档与仓库实践一条生产级 JSON 处理链路应当包含四层防护解析兜底所有JSON.parse()必须包在try/catch中返回安全默认值形状校验解析成功后用typeof检查、Array.isArray或自定义断言验证结构再访问深层属性Schema 强校验对来自 API、localStorage、用户输入的外部数据用 Zod / Valibot 定义 SchemasafeParse拿到类型安全的data序列化与响应预检JSON.stringify前确认数据可序列化无undefined、函数、循环引用fetch后先验 HTTP 状态与Content-Type再解析。从 storage 存储层 的getLocal到 schemas 校验层 的userProgressSchema再到 use-progress Hook 的 API 数据闸门Front-End-Checklist 仓库本身就是这套方法论的最佳注脚凡是外部数据进入应用的地方都必须先经过解析兜底与结构校验两道闸门。【免费下载链接】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),仅供参考