ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

io-ts 编码器(Encoder)完全指南:Encoder 接口、组合子与静态类型提取

io-ts 编码器(Encoder)完全指南:Encoder 接口、组合子与静态类型提取 后端【免费下载链接】io-tsRuntime type system for IO decoding/encoding项目地址https://gitcode.com/gh_mirrors/io/io-ts点击查看免费下载导读io-ts是一个 TypeScript 运行时类型系统专注于 IO 的 decode解码与 encode编码。本文聚焦其实验性模块io-ts/Encoder自 v2.2.3 引入讲解如何用EncoderO, A接口描述「领域类型 → 输出类型」的序列化映射通过nullable、struct、partial、array、tuple、sum、lazy等组合子构建可复用的编码器并借助OutputOf/TypeOf类型操作符从编码器反向提取静态类型。读完本文你将能够在 io-ts 项目中独立设计编码层并与Decoder、Codec协同完成完整的 IO 管线。注意与稳定版主模块index.ts 模块不同Encoder模块属于实验性特性Experimental。实验性功能发布旨在获得社区早期反馈处于高变动状态可能随时变更详见版本追踪 issues 与 Encoder 模块文档。当前仓库版本为 package.json 中声明的2.2.21使用本文示例时建议以你实际安装版本为准。Encoder 接口编码的最小契约Encoder模块的核心模型model是一个极简的接口定义于 src/Encoder.ts#L25-L27export interface EncoderO, A { readonly encode: (a: A) O }接口包含两个类型参数和一个方法A领域类型内部类型即编码器的输入类型O输出类型即编码后的结果类型encode一个纯函数(a: A) O负责把领域值转换为输出值。readonly修饰符意味着encode一经创建不可被重新赋值这与 fp-ts 生态「不可变数据结构」的一贯风格一致。一个最简单的编码器示例与 test/helpers.ts#L59-L61 中的辅助编码器完全一致import * as E from io-ts/Encoder const NumberToString: E.Encoderstring, number { encode: String } NumberToString.encode(1) // 1这里O string、A numberencode直接复用全局String函数类型签名完全吻合(a: number) string。为什么需要独立的 Encoder 抽象io-ts 稳定版主模块src/index.ts#L138-L140同样定义了EncoderA, O接口但它与Decoder、Type绑在一起——一个TypeA, O, I同时承担校验与编码两种职责。而实验性io-ts/Encoder模块将编码能力单独拆出使其可以独立于校验逻辑使用。从 src/Codec.ts#L32 可以看到CodecI, O, A正是D.DecoderI, A E.EncoderO, A的组合这说明Encoder是Codec的半边解码负责「外部输入 → 领域值」编码负责「领域值 → 外部输出」两者独立组合。组合子从原语构建复杂编码器Encoder模块提供了十余个组合子combinators全部定义在 src/Encoder.ts 中。它们与Decoder、Schemable的组合子一一对应参见 Schemable 接口让你可以用同一套描述语言构建解码器与编码器。nullable可空值编码这是Encoder.md文档中的官方示例。nullable接受一个内层编码器返回一个能够处理null的编码器import * as E from io-ts/Encoder export function nullableO, A(or: E.EncoderO, A): E.Encodernull | O, null | A { return { encode: (a) (a null ? null : or.encode(a)) } }行为规则与 test/Encoder.ts#L18-L22 的测试互相印证输入为null时输出null不调用内层编码器输入为非空领域值时交给内层编码器or.encode(a)处理。const encoder E.nullable(H.encoderNumberToString) // 其中 encoderNumberToString 即 String encoder.encode(1) // 1 encoder.encode(null) // null对应的源码实现在 src/Encoder.ts#L37-L41与文档示例逐字一致。struct 与 partial对象编码struct对固定字段的对象进行编码逐字段调用对应的编码器并组装成新对象src/Encoder.ts#L47-L59const encoder E.struct({ a: H.encoderNumberToString, // Encoderstring, number b: H.encoderBooleanToNumber // Encodernumber, boolean }) encoder.encode({ a: 1, b: true }) // { a: 1, b: 1 }类型签名保留了字段级映射关系Encoder{ [K in keyof P]: OutputOfP[K] }, { [K in keyof P]: TypeOfP[K] }partial用于字段全部可选的场景src/Encoder.ts#L74-L91其编码逻辑有两个值得注意的细节均有测试覆盖见 test/Encoder.ts#L29-L36缺失的字段不会出现在输出中——通过if (k in a)判断encode({ a: 1 })只输出{ a: 1 }显式传入undefined的字段会被原样保留——encode({ a: 1, b: undefined })输出{ a: 1, b: undefined }避免在序列化时丢失「键存在但值为空」的语义。const encoder E.partial({ a: H.encoderNumberToString, b: H.encoderBooleanToNumber }) encoder.encode({ a: 1, b: true }) // { a: 1, b: 1 } encoder.encode({ a: 1 }) // { a: 1 } encoder.encode({ a: 1, b: undefined }) // { a: 1, b: undefined } encoder.encode({}) // {}兼容性提示旧版名称typesrc/Encoder.ts#L68已标记deprecated官方建议统一使用struct替代对应文档见 docs/modules/Encoder.ts.md。record、array 与 tuple容器编码record(codomain)编码Recordstring, A遍历所有键并对每个值调用codomain.encodesrc/Encoder.ts#L97-L107const encoder E.record(H.encoderNumberToString) encoder.encode({ a: 1, b: 2 }) // { a: 1, b: 2 }array(item)编码数组内部实现就是as.map(item.encode)src/Encoder.ts#L113-L117const encoder E.array(H.encoderNumberToString) encoder.encode([1, 2]) // [1, 2]tuple(...components)按位置逐项编码元组src/Encoder.ts#L123-L129const encoder E.tuple(H.encoderNumberToString, H.encoderBooleanToNumber) encoder.encode([3, true]) // [3, 1]intersect 与 sum组合与可辨识联合intersect将两个编码器「合并」为一个src/Encoder.ts#L135-L139。它通过柯里化接口(right) (left)支持pipe链式调用底层使用Schemable导出的intersect_函数合并两个对象import { pipe } from fp-ts/lib/pipeable const encoder pipe( E.struct({ a: H.encoderNumberToString }), E.intersect(E.struct({ b: H.encoderBooleanToNumber })) ) encoder.encode({ a: 1, b: true }) // { a: 1, b: 1 }sum用于可辨识联合tagged union编码src/Encoder.ts#L145-L155。它接受一个 tag 字段名返回一个根据members分派编码器的函数运行时通过members[a[tag]]找到对应分支并委托编码const S1 E.struct({ _tag: E.idA(), a: H.encoderNumberToString }) const S2 E.struct({ _tag: E.idB(), b: H.encoderBooleanToNumber }) const encoder E.sum(_tag)({ A: S1, B: S2 }) encoder.encode({ _tag: A, a: 1 }) // { _tag: A, a: 1 } encoder.encode({ _tag: B, b: true }) // { _tag: B, b: 1 }lazy递归结构的编码对于树、链表等递归数据类型lazy允许你延迟引用尚未定义完成的编码器src/Encoder.ts#L161-L166。其内部通过Schemable导出的memoize对工厂函数做记忆化确保递归定义只被求值一次interface A { a: number; bs: ArrayB } interface B { b: boolean; as: ArrayA } const A: E.EncoderAOut, A E.lazy(() E.struct({ a: H.encoderNumberToString, bs: E.array(B) }) ) const B: E.EncoderBOut, B E.lazy(() E.struct({ b: H.encoderBooleanToNumber, as: E.array(A) }) ) A.encode({ a: 1, bs: [{ b: true, as: [{ a: 2, bs: [] }] }] }) // { a: 1, bs: [{ b: 1, as: [{ a: 2, bs: [] }] }] }test/Encoder.ts的lazy用例test/Encoder.ts#L70-L106完整演示了 A/B 互相递归的编码场景。readonly 与 idreadonly是一个恒等包装EncoderO, ReadonlyA实现即identitysrc/Encoder.ts#L172运行时零开销只做类型层面的转换自 v2.2.16 引入idA()返回EncoderA, Aencode就是恒等函数src/Encoder.ts#L206-L210常用于sum的 tag 字段如上面的E.idA()或表示「无需转换」的编码器。管道化操作contramap、compose 与实例EncoderO, A在类型参数上对第一个参数A是逆变的Contravariant因此可以contramap(f)先对输入应用函数f再做编码src/Encoder.ts#L192-L193const encoder E.contramap((s: string) s.length)(H.encoderNumberToString) encoder.encode(aaa) // 3compose(ab)组合两个编码器encode: (b) ea.encode(ab.encode(b))src/Encoder.ts#L199-L200const encoder pipe(H.encoderBooleanToNumber, E.compose(H.encoderNumberToString)) encoder.encode(true) // 1先 boolean→number再 number→string模块还导出了 fp-ts 风格的实例instancesURI io-ts/Encodersrc/Encoder.ts#L220、Contravariantv2.2.8与Categoryv2.2.8使Encoder可以接入 fp-ts 的泛型编程与HKT机制。从编码器提取静态类型OutputOf 与 TypeOfEncoder.md文档的第二个核心主题是编码器本身可以充当类型的证明。通过OutputOf类型操作符可以从编码器反向推导出输出类型const NumberToString: E.Encoderstring, number { encode: String } type MyOutputType E.OutputOftypeof NumberToString /* type MyOutputType string */OutputOf的定义src/Encoder.ts#L265export type OutputOfE E extends Encoderinfer O, any ? O : never配套的TypeOfsrc/Encoder.ts#L260提取输入领域类型export type TypeOfE E extends Encoderany, infer A ? A : never对组合子而言这两个操作符能够递归地还原结构。例如从struct的签名src/Encoder.ts#L47-L49可以看出struct输出的EncoderO, A中O与A正是对每个字段分别取OutputOf/TypeOf后重建的对象类型。这意味着编码器既是运行时的值可执行encode又是编译期的类型描述可提取OutputOf/TypeOf得到静态类型。配合Decoder模块你可以在Decoder上提取输入/领域类型、在Encoder上提取输出类型从而保证「解码结果」与「编码输入」两侧的静态类型都从代码中唯一推导避免手写重复的接口定义。与 Decoder、Codec 协同的完整 IO 管线Codec模块把Decoder与Encoder合二为一src/Codec.ts#L32并提供两个构造器make(decoder, encoder)由独立的解码器与编码器组装成 CodecfromDecoder(decoder)解码器 恒等编码器适用于无需转换输出的场景。典型用法是用Decoder校验未知输入并转成领域类型用Encoder把领域类型序列化为传输格式如 JSON 友好的字符串、数字枚举等两者由Codec绑定形成unknown → A解码与A → O编码的完整闭环。io-ts 对 Codec 的编码方向有两条律src/Codec.ts#L24-L28解码失败时回退为原值pipe(codec.decode(u), E.fold(() u, codec.encode)) u编码后再解码可还原codec.decode(codec.encode(a)) E.right(a)。这两条律确保了 encode/decode 方向互为逆操作是设计自定义编码器时值得遵循的准则。组合子速查表以下汇总io-ts/Encoder的全部公开 API签名与版本号来自 docs/modules/Encoder.ts.md 与 src/Encoder.ts分类名称签名引入版本modelEncoderinterface EncoderO, A { readonly encode: (a: A) O }2.2.3combinatorsnullable(or: EncoderO, A) Encodernull \| O, null \| A2.2.3combinatorsstruct(properties) Encoder{...OutputOf...}, {...TypeOf...}2.2.15combinatorstypetypeof struct已废弃用struct2.2.3combinatorspartial(properties) EncoderPartial..., Partial...2.2.3combinatorsrecord(codomain: EncoderO, A) EncoderRecordstring, O, Recordstring, A2.2.3combinatorsarray(item: EncoderO, A) EncoderArrayO, ArrayA2.2.3combinatorstuple(...components) Encoder{...}, {...}2.2.3combinatorsintersect(right) (left) EncoderO P, A B2.2.3combinatorssum(tag) (members) EncoderOutputOf..., TypeOf...2.2.3combinatorslazy(f: () EncoderO, A) EncoderO, A2.2.3combinatorsreadonly(decoder) EncoderO, ReadonlyA2.2.16Contravariantcontramap(f: (b: B) A) (fa) EncoderE, B2.2.3Semigroupoidcompose(ea) (ab) EncoderE, B2.2.3Categoryid() EncoderA, A2.2.3instancesURI/Contravariant/Categoryfp-ts 实例2.2.3 / 2.2.8utilsTypeOfE extends Encoderany, infer A ? A : never2.2.3utilsOutputOfE extends Encoderinfer O, any ? O : never2.2.3测试与验证本仓库通过 test/Encoder.tsvitest 并发用例覆盖了上述全部组合子contramap、compose、nullable、struct、partial、record、array、tuple、intersect、sum、lazy每个用例都给出了输入与期望输出的精确断言可作为你编写自定义编码器时的行为参考。测试所用的基础编码器定义在 test/helpers.ts#L59-L65即encoderNumberToStringString与encoderBooleanToNumber布尔转 0/1是理解本文所有示例的最小素材。如果你想在本地运行这些测试仓库的package.json提供了相关脚本package.jsonnpm test含 lint、dtslint、vitest 与文档生成、npm run vitest仅运行单元测试。注意io-ts以fp-ts^2.5.0为 peer dependency安装时需一并安装npm i io-ts fp-ts。总结EncoderO, A是一个单方法接口encode: (a: A) O用最少的契约描述「领域值 → 输出值」的转换通过nullable、struct、partial、record、array、tuple、intersect、sum、lazy、readonly等组合子可以像搭积木一样构建任意复杂度的编码器且每个组合子的类型签名都会精确保留字段/元素的类型映射OutputOf/TypeOf让编码器同时充当编译期类型描述的来源静态类型从代码中自动推导杜绝类型定义与实现漂移在 io-ts 的完整管线中Encoder与Decoder互为镜像由Codec组合成可逆的编解码闭环。继续深入学习可参考仓库中的 Encoder.md本文档、模块 API 文档、Decoder.md、Codec.md 以及稳定版主模块的 index.md。赞分享后端【免费下载链接】io-tsRuntime type system for IO decoding/encoding项目地址https://gitcode.com/gh_mirrors/io/io-ts点击查看免费下载相关推荐MAS 激活脚本免费一键激活 Windows 和 Office 的开源方案MAS 激活脚本免费一键激活 Windows 和 Office 的开源方案 装完 Win11右下角挂出「你的 Windows 未激活」Office 打开也操作系统Shutter Encoder专业视频编码工具完全指南Shutter Encoder专业视频编码工具完全指南 Shutter Encoder是一款基于Java开发的专业视频编码工具采用FFmpeg作为核心引擎为音视频桌面应用DeepSpeed-VisualChat 视觉编码器提取指南从 QWen-VL 中剥离 Vision Encoder 并接入多模态训练流水线DeepSpeed VisualChat 视觉编码器提取指南从 QWen VL 中剥离 Vision Encoder 并接入多模态训练流水线 导读 本文围绕示例工程上一篇Poe the Poet与现代化Python工具链集成Ruff、Black、Mypy完美配合下一篇SwarmForge性能测试评估AI代理协作的效率创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表