拓冰建站拓冰建站
首页 / 资讯中心 / 正文

fp-ts Json 模块完全指南:基于 Either 的安全 JSON 解析与序列化

fp-ts Json 模块完全指南基于 Either 的安全 JSON 解析与序列化【免费下载链接】fp-tsFunctional programming in TypeScript项目地址: https://gitcode.com/gh_mirrors/fp/fp-ts导读fp-ts/Json模块自 v2.10.0 起提供为 TypeScript 函数式编程提供了不抛异常的 JSON 解析与序列化方案它把 JavaScript 内置的JSON.parse/JSON.stringify包装成返回Eitherunknown, Json的纯函数让错误成为可组合、可追踪的一等公民。读完本文你将掌握Json、JsonArray、JsonRecord三个核心类型的准确含义学会用parse安全解析字符串、用stringify安全序列化任意值并能通过pipe、Either的各种组合子把它们无缝接入 fp-ts 的任意函数式管线中。一、模块概览为什么 JSON 操作需要函数式封装JavaScript 原生的JSON.parse与JSON.stringify是可能抛异常的函数解析一段非法 JSON 会抛出SyntaxError序列化含循环引用的对象会抛出TypeError。在 fp-ts 的编程范式里异常流无法被pipe、map、chain等组合子感知会破坏类型的纯净性。Json模块给出的答案是用 tryCatch位于src/Either.ts把这两个 API 包装成返回Eitherunknown, Json的纯函数。tryCatch的核心实现非常直白见 src/Either.ts#L1393-L1399export const tryCatch E, A(f: LazyArgA, onThrow: (e: unknown) E): EitherE, A { try { return right(f()) } catch (e) { return left(onThrow(e)) } }即正常返回right(结果)任何异常都经onThrow转换后放入left。Json模块传给onThrow的是identity见 src/Json.ts因此错误类型就是unknown原始异常对象如SyntaxError、TypeError会被原样保留在Either的左侧供后续mapLeft精确检查。从模块导出看src/index.ts在第 58 行import * as json from ./Json、第 385 行将整个模块作为json命名空间导出因此你可以用import * as J from fp-ts/Json或import { json } from fp-ts两种方式引入。二、类型体系Json / JsonArray / JsonRecordJson模块定义了三个互相递归的类型完整刻画了合法 JSON 值的形态见 src/Json.ts#L10-L22export type Json boolean | number | string | null | JsonArray | JsonRecord export interface JsonRecord { readonly [key: string]: Json } export interface JsonArray extends ReadonlyArrayJson {}类型定义说明Jsonboolean \| number \| string \| null \| JsonArray \| JsonRecordJSON 值的联合类型即值层面的完整定义JsonRecordreadonly [key: string]: Json键为字符串、值为Json的只读记录即 JSON 对象JsonArrayextends ReadonlyArrayJsonJson元素的只读数组即 JSON 数组几个值得注意的设计细节没有undefined、函数、Symbol、bigint这些都不是合法 JSON 值因而被类型系统直接排除。dtslint/Json.ts中用ts-expect-error断言了undefined、箭头函数、Symbol()、含undefined字段的对象等都无法通过stringifyJson的类型检查见 dtslint/Json.ts#L9-L20。对象与数组是只读readonly形态JsonRecord的索引签名和JsonArray的数组类型都带readonly与 fp-ts 一贯倡导的不可变数据结构风格一致也方便与ReadonlyArray、ReadonlyRecord等模块对接。递归结构Json引用JsonArray与JsonRecord而后两者又引用Json从而支持任意深度的嵌套 JSON对象套数组、数组套对象……。三、parse安全解析 JSON 字符串parse将一段 JSON 字符串转换为Json类型对应JSON.parse其签名与实现如下export declare const parse: (s: string) Eitherunknown, Json // 实现src/Json.ts#L37 export const parse (s: string): Eitherunknown, Json tryCatch(() JSON.parse(s), identity)行为语义输入合法 JSON→ 返回E.right(json)其中json已被JSON.parse反序列化为实际 JavaScript 值输入非法 JSON→ 底层JSON.parse抛出SyntaxError被tryCatch捕获后包装为E.left(syntaxError)异常对象本身原样保留。官方示例可直接运行import * as J from fp-ts/Json import * as E from fp-ts/Either import { pipe } from fp-ts/function assert.deepStrictEqual(pipe({a:1}, J.parse), E.right({ a: 1 })) assert.deepStrictEqual( pipe({a:}, J.parse), E.left(new SyntaxError(Unexpected token }, {a:} is not valid JSON)) )上面第二个断言展示了一个关键事实解析失败的left分支携带的是真实的SyntaxError实例其message与原生JSON.parse抛出的完全一致便于日志记录或mapLeft分类处理。测试验证在 test/Json.ts#L7-L13 中这两个场景均被测试用例覆盖it(parse, () { U.deepStrictEqual(pipe({a:1}, _.parse), E.right({ a: 1 })) U.deepStrictEqual( pipe({a:}, _.parse), E.left(new SyntaxError(Unexpected token }, {a:} is not valid JSON)) ) })四、stringify安全序列化任意 JavaScript 值stringify将任意 JavaScript 值转换为 JSON 字符串对应JSON.stringify签名与实现如下export declare const stringify: A(a: A) Eitherunknown, string // 实现src/Json.ts#L60-L67 export const stringify A(a: A): Eitherunknown, string tryCatch(() { const s JSON.stringify(a) if (typeof s ! string) { throw new Error(Converting unsupported structure to JSON) } return s }, identity)行为语义与裸用JSON.stringify相比stringify有两个层次的保护异常捕获JSON.stringify遇到循环引用时会抛出TypeError: Converting circular structure to JSON该异常被tryCatch捕获并放入left。返回类型守卫JSON.stringify在遇到undefined、函数或Symbol作为顶层输入时会返回undefined而非字符串。实现里显式检查typeof s ! string一旦出现这种情况就抛出new Error(Converting unsupported structure to JSON)从而保证right分支永远是真正的字符串杜绝看似成功实则拿到 undefined的隐性 bug。官方示例可直接运行import * as E from fp-ts/Either import * as J from fp-ts/Json import { pipe } from fp-ts/function assert.deepStrictEqual(J.stringify({ a: 1 }), E.right({a:1})) const circular: any { ref: null } circular.ref circular assert.deepStrictEqual( pipe( J.stringify(circular), E.mapLeft((e) e instanceof Error e.message.includes(Converting circular structure to JSON)) ), E.left(true) )第二个断言展示了stringify与E.mapLeft的经典配合序列化失败不再是静默的运行时异常而是可以被函数式地检查、映射与分支处理。测试验证test/Json.ts#L15-L35 覆盖了四种典型输入输入期望结果{ a: 1 }E.right({a:1})含循环引用的对象E.left(...)且message含Converting circular structure to JSON类型化对象{ name: Giulio, age: 45 }E.right({name:Giulio,age:45})undefined顶层E.left(new Error(Converting unsupported structure to JSON))其中最后一个用例直接验证了返回类型守卫分支_.stringify(undefined as any)必须返回E.left(new Error(Converting unsupported structure to JSON))。类型层面的约束虽然stringify的类型签名是A(a: A) Eitherunknown, string接受任意类型但配合Json类型使用时可获得更强的编译期保障。dtslint/Json.ts展示了类型测试的意图以下调用必须报类型错误ts-expect-errorstringifyJson(undefined)、stringifyJson(() {})、stringifyJson(Symbol())、stringifyJson({ a: undefined })见 dtslint/Json.ts#L9-L20而合法的 JSON 形状如{ a: a, b: 1 }及其数组、展开副本可以顺利通过见 dtslint/Json.ts#L22-L37。五、组合使用把 parse / stringify 接入函数式管线由于parse与stringify都返回Either它们可以无缝接入 fp-ts 的Either组合子体系。官方示例和 dtslint 用例给出了典型用法1. 链式校验与转换import * as E from fp-ts/Either import { pipe } from fp-ts/function // 解析 → 校验 → 转换全程类型安全 const result pipe( {a:1}, J.parse, E.chain((json) /* 在这里对 json 做进一步校验/转换返回 Eitherunknown, X */ E.right(json)), E.map((x) /* 转换成功后的值 */ x) )2. 用 chainFirst 挂接副作用dtslint 中的用例pipe(E.right(a), E.chainFirst(_.stringify))见 dtslint/Json.ts#L39-L40表明stringify可直接用于chainFirst在保持左侧值不变的同时把序列化结果作为校验步骤——若序列化失败整个管道立即短路为left。// $ExpectType Eitherunknown, string pipe(E.right(a), E.chainFirst(_.stringify))3. 错误分类处理const classify (e: unknown): string e instanceof SyntaxError ? JSON 语法错误: ${e.message} : e instanceof Error ? 其他错误: ${e.message} : 未知错误: ${String(e)} const safeParse (s: string) pipe(s, J.parse, E.mapLeft(classify))六、与其他模块的关系及版本背景依赖Json模块仅依赖EithertryCatch与functionidentity依赖面极小可放心引入见 src/Json.ts#L4-L5。版本引入该模块于 v2.10.0 加入CHANGELOG 中 addJsonmodule 条目。CHANGELOG 同时记录了后续版本中原先分散在其他模块的Json类型、parseJSON、stringifyJSON等旧 API 被标记废弃官方明确建议使用Json模块替代见 CHANGELOG.md。因此新代码应统一从fp-ts/Json导入。七、小结与使用建议fp-ts/Json模块的价值在于把最容易悄悄抛异常的两个 JSON 操作变成了显式、可组合、类型可追踪的Either计算。核心要点回顾Json/JsonArray/JsonRecord三个递归类型精确刻画了合法 JSON 值天然排除undefined、函数等非法输入parse返回Eitherunknown, Json解析失败时左侧携带原始SyntaxErrorstringify返回Eitherunknown, string既捕获循环引用异常又用显式检查杜绝顶层值为 undefined 却返回非字符串的隐患二者均可通过pipe、chain、chainFirst、mapLeft等组合子接入任意 Either 管线错误处理从try/catch 包围升级为数据流中的分支。推荐的使用姿势在解析外部输入HTTP 响应体、配置文件、本地存储读取时一律用J.parseE.mapLeft显式分类错误在序列化可能含循环引用或顶层非字符串值的数据时使用J.stringify并在left分支统一记录日志。这样既保留了原生JSONAPI 的性能与语义又让整个错误路径始终处于类型系统的掌控之中。【免费下载链接】fp-tsFunctional programming in TypeScript项目地址: https://gitcode.com/gh_mirrors/fp/fp-ts创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门