
后端【免费下载链接】io-tsRuntime type system for IO decoding/encoding项目地址https://gitcode.com/gh_mirrors/io/io-ts点击查看免费下载io-ts 的Decoder模块io-ts/Decoder提供了一套以「输入类型I→ 输出类型A」为模型的运行时解码器其核心是一个形如DecoderI, A { decode: (i: I) EitherDecodeError, A }的接口。本文以仓库内 docs/modules/Decoder.ts.md 的 API 参考为骨架结合 src/Decoder.ts、src/Kleisli.ts、src/DecodeError.ts、src/FreeSemigroup.ts 的源码实现与 test/Decoder.ts 的测试用例系统讲解该模块的模型、原始解码器、构造器、组合器、高层抽象实例与类型工具。读完本文你将掌握如何用Decoder从unknown输入构建可复用的运行时校验器理解其「一次收集全部错误、输出树状错误报告」的设计原理并能用TypeOf/InputOf直接推导静态类型。模块定位与实验性状态Decoder模块是 io-ts 在v2.2.7开始发布的实验性模块package.json 当前版本为 2.2.21。按照仓库 README.md 的说明实验性模块*发布目的是尽早获得社区反馈其 API 处于高度变动状态可能在没有通知的情况下发生破坏性变更。README 同时强调这些实验性模块与稳定版功能是独立且不向后兼容的——也就是说io-ts/Decoder的用法不能直接与稳定版io-ts主模块src/index.ts的t.type、t.Type混用。安装方式与稳定版相同fp-ts是 peer 依赖版本要求^2.5.0npm i io-ts fp-ts使用时按模块导入import * as D from io-ts/Decoder核心模型DecoderI, A接口定义文档将模型定义为一组带decode方法的接口interface DecoderI, A { readonly decode: (i: I) E.EitherDecodeError, A }在源码 src/Decoder.ts#L79 中DecoderI, A被正式声明为对 Kleisli 抽象的一种特化export interface DecoderI, A extends K.KleisliE.URI, I, DecodeError, A {}这里的K.Kleisli来自 src/Kleisli.ts#L33export interface KleisliM extends URIS2, I, E, A { readonly decode: (i: I) Kind2M, E, A }也就是说Decoder是「monadMfp-ts的EitherE.URI、错误类型EDecodeError」这一 Kleisli 配置下的具体形态。decode返回E.EitherDecodeError, A遵循 fp-ts 的约定Left表示失败、Right表示成功。手写一个原始解码器文档给出的string解码器示例展示了模型的最小实现import * as D from io-ts/Decoder export const string: D.Decoderunknown, string { decode: (u) (typeof u string ? D.success(u) : D.failure(u, string)) }使用isRight与fold处理解码结果import { isRight } from fp-ts/Either import { pipe } from fp-ts/function import { fold } from fp-ts/Either console.log(isRight(string.decode(a))) // true console.log(isRight(string.decode(null))) // false console.log( pipe( string.decode(null), fold( // failure handler (errors) error: ${JSON.stringify(errors)}, // success handler (a) success: ${JSON.stringify(a)} ) ) ) // error: {_tag:Of,value:{_tag:Leaf,actual:null,error:string}}注意失败分支的 JSON 结构_tag: Of是FreeSemigroup的节点内部_tag: Leaf是DecodeError的叶子节点——这正对应下文要讲的错误模型。错误模型DecodeError、FreeSemigroup与错误聚合DecodeError类型别名文档中定义export type DecodeError FS.FreeSemigroupDE.DecodeErrorstring它由两部分组成DE.DecodeErrorstring来自 src/DecodeError.ts#L101 的判别联合共 6 种节点节点含义携带信息Leaf最底层的单条错误actual实际值、error期望描述Key对象属性错误key、kindrequired/optional、子错误Index数组元素错误index、kindrequired/optional、子错误Member联合成员错误index成员序号、子错误Lazy递归类型错误id递归类型名、子错误Wrap自定义消息包裹error自定义消息、子错误FS.FreeSemigroup来自 src/FreeSemigroup.ts#L36只有两种节点OfA单个值与ConcatA左右拼接。它的作用是在不解糖的情况下无损保存多个错误使得解码器可以「一次性收集所有属性/元素的错误」而不是失败即停。三个错误构造器export const error (actual: unknown, message: string): DecodeError export const failure A never(actual: unknown, message: string) E.EitherDecodeError, A export const success A(a: A) E.EitherDecodeError, A源码实现非常薄src/Decoder.ts#L95-L108error即FS.of(DE.leaf(actual, message))failure即E.left(error(...))success即E.right。错误聚合的底层机制Decoder之所以能聚合错误根源在 src/Decoder.ts#L33-L69 定义的 Kleisli 配置SE DE.getSemigroupstring()提供了FreeSemigroup的拼接操作ap在左右两侧都失败时用SE.concat合并两者。而 src/Kleisli.ts 中的fromStruct、fromPartial、fromArray、fromRecord、fromTuple都基于traverseRecordWithIndex/traverseArrayWithIndex对每个键或索引独立执行decode再用M.ap组合结果从而把所有子错误汇聚到同一个FreeSemigroup中。测试 test/Decoder.ts#L190-L204 验证了这一点D.struct({ a: D.string, b: D.number }).decode({})会同时返回a、b两条required错误。draw树状错误报告export declare const draw: (e: DecodeError) stringdrawsrc/Decoder.ts#L611把FreeSemigroup先折叠成TreestringtoForest用显式栈迭代实现再绘制成带├─/└─缩进的树形文本。测试 test/Decoder.ts#L541-L556 证明其对 10000 条记录的深度错误是栈安全的。示例输出required property name └─ cannot decode undefined, should be string required property age └─ cannot decode undefined, should be number原始解码器primitives文档列出的 5 个原始解码器均从 v2.2.7 起提供export declare const string: Decoderunknown, string export declare const number: Decoderunknown, number export declare const boolean: Decoderunknown, boolean export declare const UnknownArray: Decoderunknown, unknown[] export declare const UnknownRecord: Decoderunknown, Recordstring, unknown源码中它们全部通过fromGuard基于 src/Guard.ts 的类型守卫构建src/Decoder.ts#L146-L180export const string: Decoderunknown, string fromGuard(G.string, string) export const number: Decoderunknown, number fromGuard(G.number, number) export const boolean: Decoderunknown, boolean fromGuard(G.boolean, boolean) export const UnknownArray: Decoderunknown, Arrayunknown fromGuard(G.UnknownArray, Arrayunknown) export const UnknownRecord: Decoderunknown, Recordstring, unknown fromGuard(G.UnknownRecord, Recordstring, unknown)第二个参数是错误消息里的期望描述如string、Arrayunknown。注意测试 test/Decoder.ts#L47-L49 特别验证了number会拒绝NaN返回Leaf(NaN, number)与Guard.number的实现一致。构造器constructorsfromRefinement与fromGuardexport declare const fromRefinement: I, A extends I(refinement: RefinementI, A, expected: string) DecoderI, A export declare const fromGuard: I, A extends I(guard: G.GuardI, A, expected: string) DecoderI, AfromRefinement接受一个类型细化函数RefinementI, A与期望描述fromGuard则把G.Guard的is方法转发给fromRefinementsrc/Decoder.ts#L118-L126。底层 src/Kleisli.ts#L45-L51 的fromRefinement实现为细化通过返回M.of(i)否则返回M.throwError(onError(i))。literalexport declare const literal: A extends readonly [L, ...L[]], L extends S.Literal S.Literal( ...values: A ) Decoderunknown, A[number]描述一个或多个字面量值。失败消息会把所有合法值JSON.stringify后用 | 连接src/Decoder.ts#L132-L136D.literal(a) // Decoderunknown, a D.literal(a, b) // Decoderunknown, a | b D.literal(a, null, b, 1, true) // 混合类型字面量测试 test/Decoder.ts#L71-L82 验证D.literal(a, null).decode(b)的错误消息为a | null。组合器combinators组合器用于把原始解码器组合成领域模型。文档列出了两套命名风格fromXxx系列保留精确输入/输出类型与xxx系列输入固定为unknown。从源码结构看xxx系列几乎都是「先验证未知容器再compose对应的fromXxx」的便捷封装。struct/fromStruct以及已废弃的type/fromTypeexport declare const struct: A(properties: { [K in keyof A]: Decoderunknown, A[K] }) Decoderunknown, { [K in keyof A]: A[K] } export declare const fromStruct: P extends Recordstring, Decoderany, any( properties: P ) Decoder{ [K in keyof P]: K.InputOfEither, P[K] }, { [K in keyof P]: K.TypeOfEither, P[K] }struct描述必填字段对象。源码src/Decoder.ts#L252-L255显示它是pipe(UnknownRecord, compose(fromStruct(properties)))即先保证输入是Recordstring, unknown再逐键解码。fromStruct的键错误用DE.key(k, DE.required, e)标记为requiredsrc/Decoder.ts#L234-L237。行为要点均有测试佐证见 test/Decoder.ts#L157-L218解码时剥离额外字段Person.decode({ name: name, age: 42, rememberMe: true })只保留{ name, age }缺失的必填字段报required property错误支持 getter对类实例解码。export const Person D.struct({ name: D.string, age: D.number }) console.log(isRight(Person.decode({ name: name, age: 42 }))) // true console.log(isRight(Person.decode({ name: name }))) // falsetype/fromType已废弃文档明确标注「Usestruct/fromStructinstead」源码中二者只是别名src/Decoder.ts#L246、src/Decoder.ts#L264。partial/fromPartialexport declare const partial: A(properties: { [K in keyof A]: Decoderunknown, A[K] }) Decoderunknown, Partial{ [K in keyof A]: A[K] } export declare const fromPartial: P extends Recordstring, Decoderany, any( properties: P ) DecoderPartial{ [K in keyof P]: K.InputOfEither, P[K] }, Partial{ [K in keyof P]: K.TypeOfEither, P[K] }描述可选字段对象键错误标记为DE.optional。fromPartial的实现src/Kleisli.ts#L162-L193对undefined有精细语义测试 test/Decoder.ts#L220-L266 逐一验证缺失的属性不会被添加到结果中decode({})得到{}而不是{ a: undefined }显式为undefined的属性不会被剥离保留{ a: undefined }额外字段被剥离。export const Person D.partial({ name: D.string, age: D.number }) console.log(isRight(Person.decode({ name: name, age: 42 }))) // true console.log(isRight(Person.decode({ name: name }))) // truerecord/fromRecordexport declare const record: A(codomain: Decoderunknown, A) Decoderunknown, Recordstring, A export declare const fromRecord: I, A(codomain: DecoderI, A) DecoderRecordstring, I, Recordstring, A描述Recordstring, ?对每个键的值用同一个codomain解码键错误为DE.key(k, DE.optional, e)src/Decoder.ts#L302-L303export const MyRecord: D.Decoderunknown, Recordstring, number D.record(D.number) console.log(isRight(MyRecord.decode({ a: 1, b: 2 }))) // true测试 test/Decoder.ts#L295-L323 验证多个键同时出错时错误会被全部收集。array/fromArrayexport declare const array: A(item: Decoderunknown, A) Decoderunknown, A[] export declare const fromArray: I, A(item: DecoderI, A) DecoderI[], A[]描述数组元素错误标记为DE.index(i, DE.optional, e)export const MyArray: D.Decoderunknown, Arraynumber D.array(D.number) console.log(isRight(MyArray.decode([1, 2, 3]))) // truetuple/fromTupleexport declare const tuple: A extends readonly unknown[]( ...components: { [K in keyof A]: Decoderunknown, A[K] } ) Decoderunknown, A export declare const fromTuple: C extends readonly Decoderany, any[]( ...components: C ) Decoder{ [K in keyof C]: K.InputOfEither, C[K] }, { [K in keyof C]: K.TypeOfEither, C[K] }描述 n 元组位置错误标记为DE.index(i, DE.required, e)src/Decoder.ts#L316-L319。会剥离多余的尾部元素export const MyTuple: D.Decoderunknown, [string, number] D.tuple(D.string, D.number) console.log(isRight(MyTuple.decode([a, 1]))) // true console.log(MyTuple.decode([a, 1, true])) // { _tag: Right, right: [ a, 1 ] }测试还覆盖了零元素元组D.tuple().decode([])、缺元素与类型错误的聚合场景test/Decoder.ts#L325-L365。intersectexport declare const intersect: IB, B(right: DecoderIB, B) IA, A(left: DecoderIA, A) DecoderIA IB, A B描述交叉类型对象合并常用来混合必填与可选属性。底层实现src/Kleisli.ts#L271-L282用M.ap同时解码两侧并合并错误export const Person pipe( D.struct({ name: D.string }), D.intersect(D.partial({ age: D.number })) ) console.log(isRight(Person.decode({ name: name }))) // true console.log(isRight(Person.decode({}))) // false测试 test/Decoder.ts#L408-L439 验证两侧错误会同时累积{ a: a }只报缺b{}同时报缺a和b且支持对原始类型如整数与正数做组合。unionexport declare const union: MS extends readonly [Decoderany, any, ...Decoderany, any[]]( ...members: MS ) DecoderK.InputOfEither, MS[keyof MS], K.TypeOfEither, MS[keyof MS]描述无标签联合按顺序逐个尝试成员失败错误以DE.member(i, e)包裹并合并src/Kleisli.ts#L248-L265const MyUnion D.union(D.string, D.number) console.log(isRight(MyUnion.decode(a))) // true console.log(isRight(MyUnion.decode(1))) // true console.log(isRight(MyUnion.decode(null))) // falsesum/fromSumexport declare const sum: T extends string(tag: T) A( members: { [K in keyof A]: Decoderunknown, A[K] RecordT, K } ) Decoderunknown, A[keyof A] export declare const fromSum: T extends string(tag: T) MS extends Recordstring, Decoderany, any( members: MS ) DecoderK.InputOfEither, MS[keyof MS], K.TypeOfEither, MS[keyof MS]描述带标签联合sum type。fromSum的调度逻辑src/Kleisli.ts#L288-L316读取ir[tag]若该值恰好是members的键则分派到对应成员解码器否则报错文档示例中未知 tag 值的错误消息为A | B由 src/Decoder.ts#L351-L362 的onTagError生成空成员表时为neverexport const MySum: D.Decoder unknown, | { type: A; a: string } | { type: B; b: number } D.sum(type)({ A: D.struct({ type: D.literal(A), a: D.string }), B: D.struct({ type: D.literal(B), b: D.number }) })非字符串 tag时键需用方括号包裹文档强调该用法测试 test/Decoder.ts#L479-L490 亦覆盖D.sum(type)({ [1]: D.struct({ type: D.literal(1), a: D.string }), [2]: D.struct({ type: D.literal(2), b: D.number }) })nullableexport declare const nullable: I, A(or: DecoderI, A) DecoderI | null, A | null描述可空值输入为null时直接成功否则委托给or失败时把「期望 null」与「or的失败」分别包装为member 0与member 1合并src/Decoder.ts#L226-L228。测试 test/Decoder.ts#L127-L155 展示了decode(undefined)会同时报告两条 member 错误。lazyexport declare const lazy: I, A(id: string, f: () DecoderI, A) DecoderI, A支持递归与互递归解码器。实现用S.memoize缓存工厂函数结果src/Kleisli.ts#L322-L332失败错误以DE.lazy(id, e)包裹。递归示例interface Category { title: string subcategory: null | Category } const Category: D.Decoderunknown, Category D.lazy(Category, () D.struct({ title: D.string, subcategory: D.nullable(Category) }) )互递归示例文档给出测试 test/Decoder.ts#L493-L535 中有类似结构interface Foo { foo: string; bar: null | Bar } interface Bar { bar: number; foo: null | Foo } const Foo: D.Decoderunknown, Foo D.lazy(Foo, () D.struct({ foo: D.string, bar: D.nullable(Bar) }) ) const Bar: D.Decoderunknown, Bar D.lazy(Bar, () D.struct({ bar: D.number, foo: D.nullable(Foo) }) )refineexport declare const refine: A, B extends A(refinement: RefinementA, B, id: string) I(from: DecoderI, A) DecoderI, B在既有解码器上叠加细化refinement常用来构造 branded 类型。细化失败时以error(a, id)报告import { pipe } from fp-ts/function export interface PositiveBrand { readonly Positive: unique symbol } export type Positive number PositiveBrand export const Positive: D.Decoderunknown, Positive pipe( D.number, D.refine((n): n is Positive n 0, Positive) ) console.log(isRight(Positive.decode(1))) // true console.log(isRight(Positive.decode(-1))) // false测试 test/Decoder.ts#L389-L406 验证了refine之前的错误如非字符串会原样保留只有细化失败才报id。parseexport declare const parse: A, B( parser: (a: A) E.EitherFS.FreeSemigroupDE.DecodeErrorstring, B ) I(from: DecoderI, A) DecoderI, B比refine更强大因为可以改变输出类型。典型场景是「字符串 → 数字」的解析import { isRight } from fp-ts/Either export const NumberFromString: D.Decoderunknown, number pipe( D.string, D.parse((s) { const n parseFloat(s) return isNaN(n) ? D.failure(s, NumberFromString) : D.success(n) }) ) console.log(isRight(NumberFromString.decode(1))) // true console.log(isRight(NumberFromString.decode(a))) // falsereadonlyexport declare const readonly: I, A(decoder: DecoderI, A) DecoderI, ReadonlyA源码 src/Decoder.ts#L385 中readonly identity即纯编译期操作运行时不产生任何开销仅把输出类型收窄为ReadonlyA。mapLeftWithInput与withMessageexport declare const mapLeftWithInput: I(f: (input: I, e: DecodeError) DecodeError) A(decoder: DecoderI, A) DecoderI, A export declare const withMessage: I(message: (input: I, e: DecodeError) string) A(decoder: DecoderI, A) DecoderI, AmapLeftWithInput可以基于输入与原始错误重写错误withMessage是它的特化把自定义消息用DE.wrap(message(...), e)包裹在错误树上src/Decoder.ts#L200-L203const decoder pipe( D.struct({ name: D.string, age: D.number }), D.withMessage(() Person) )测试 test/Decoder.ts#L96-L112 显示draw输出会以Person作为根节点标题。高层实例与抽象instances文档列出Decoder注册到 fp-ts HKT 系统的实例export declare const URI: io-ts/Decoder export declare const Functor: Functor2io-ts/Decoder export declare const Alt: Alt2io-ts/Decoder export declare const Category: Category2io-ts/Decoder export declare const Schemable: S.Schemable2Cio-ts/Decoder, unknown export declare const WithRefine: S.WithRefine2Cio-ts/Decoder, unknown export declare const WithUnion: S.WithUnion2Cio-ts/Decoder, unknown export declare const WithUnknownContainers: S.WithUnknownContainers2Cio-ts/Decoder, unknown与之配套的管式pipeable操作符即文档前部的「代数结构」小节// Functor export declare const map: A, B(f: (a: A) B) I(fa: DecoderI, A) DecoderI, B // Semigroupoid export declare const compose: A, B(to: DecoderA, B) I(from: DecoderI, A) DecoderI, B // Category export declare const id: A() DecoderA, A // Alt export declare const alt: I, A(that: () DecoderI, A) (me: DecoderI, A) DecoderI, A它们的语义在 src/Decoder.ts#L391-L431 与测试中均有体现map对解码成功的结果做变换测试 test/Decoder.ts#L15-L18compose把两个解码器串联先from.decode再to.decodeK.compose用M.chain实现测试 test/Decoder.ts#L114-L125 用pipe(D.number, D.compose(intDecoder))演示「数字 整数细化」id是恒等解码器decode: M.ofalt先尝试me失败再尝试that()都失败时合并两者错误src/Decoder.ts#L62-L68测试 test/Decoder.ts#L20-L24 验证Alt.alt(D.string, () D.number)对字符串与数字均成功。Schemable及其拆分实例的意义在于Decoder、Guard、Encoder、Eq、Codec等模块共享同一套 src/Schemable.ts 抽象如literal、struct、union、refine因此同一套 schema 描述可以派生出不同用途的实现——这也是 docs/modules/Schemable.ts.md 与 docs/modules/Codec.ts.md 所讲解的设计。类型工具utilsexport type InputOfD K.InputOfE.URI, D export type TypeOfD K.TypeOfE.URI, D export declare const draw: (e: DecodeError) stringTypeOf/InputOf从解码器实例推导静态输出/输入类型src/Kleisli.ts#L438-L443export const Person D.struct({ name: D.string, age: D.number }) export type Person D.TypeOftypeof Person // { name: string; age: number } type PersonInputType D.InputOftypeof Person // unknown也可以定义成interfaceexport interface Person extends D.TypeOftypeof Person {}类型层面的正确性由 dtslint/Decoder.ts 的$ExpectType断言持续保障例如D.struct({ a: D.literal(A) })推导为Decoderunknown, { a: A }D.fromStruct({ a: D.string, b: D.fromStruct({ c: NumberFromString }) })推导为Decoder{ a: unknown; b: { c: string } }, { a: string; b: { c: number } }——可见fromXxx系列保留了精确的输入类型信息。实战完整错误报告与集成组合所有知识点一个常见的「API 输入校验 树状错误报告」闭环如下对应文档末节「Built-in error reporter」import { isLeft } from fp-ts/Either export const Person D.struct({ name: D.string, age: D.number }) const result Person.decode({}) if (isLeft(result)) { console.log(D.draw(result.left)) } /* required property name └─ cannot decode undefined, should be string required property age └─ cannot decode undefined, should be number */draw生成的文本可以直接暴露给用户或写入日志需要结构化错误时可改用 docs/modules/Reporter.ts.md 等方案消费DecodeError树。相关模块与延伸阅读面向用户的手册式文档Decoder.md含全部组合器的使用示例抽象基座docs/modules/Kleisli.ts.mdDecoder的底层模型、docs/modules/DecodeError.ts.md错误节点定义、docs/modules/FreeSemigroup.ts.md同构模块Guard、Encoder、Codec、TaskDecoder、Eq对应 docs/modules/ 下的同名文档它们共享 Schemable 抽象源码与测试src/Decoder.ts、src/Kleisli.ts、src/DecodeError.ts、src/FreeSemigroup.ts、test/Decoder.ts、dtslint/Decoder.ts。需要再次提醒的是Decoder模块仍处于实验阶段v2.2API 可能在没有通知的情况下变化若用于生产环境请锁定版本并跟进 CHANGELOG.md。赞分享后端【免费下载链接】io-tsRuntime type system for IO decoding/encoding项目地址https://gitcode.com/gh_mirrors/io/io-ts点击查看免费下载相关推荐LibreHardwareMonitor 硬件监控指南3 步读全温度、风扇与电压LibreHardwareMonitor 硬件监控指南3 步读全温度、风扇与电压 笔记本跑一次全量编译风扇突然拉满转速温度却只能靠猜。LibreHardw后端OpenCore Legacy Patcher 完整新手指南如何让旧 Mac 免费安装最新 macOSOpenCore Legacy Patcher 完整新手指南如何让旧 Mac 免费安装最新 macOS 如果你的 Mac 是 Intel 芯片、多年停在老系统后端io-ts Decoder 模块完全指南从运行时解码到类型安全的数据校验io ts Decoder 模块完全指南从运行时解码到类型安全的数据校验 本指南围绕 io ts 2.2.x 的 Decoder 模块展开它是 io ts后端上一篇零基础复活老游戏DDrawCompat 三步兼容层部署指南下一篇文件同步工具SyncTrayzor完整上手指南三步完成Windows跨设备同步配置创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考