Effect Schema 新增OptionFromUndefinedOr与OptionFromNullishOr:将可选字段安全解码为Option
【免费下载链接】effectBuild production-ready applications in TypeScript项目地址: https://gitcode.com/GitHub_Trending/ef/effect
本文基于effect仓库中.changeset/pre/add-schema-option-from-undefined-nullish.md变更记录,系统讲解 Effect Schema 新增的OptionFromUndefinedOr与OptionFromNullishOr两个解码器/编码器(schema)的用途、语义、底层实现与实战用法,帮助你在解析 API 响应、配置文件或数据库记录时,把undefined/null这类"缺省"值统一建模为类型安全的Option<T>。
变更背景:为什么要把undefined/null映射为Option
在 TypeScript 中,undefined与null常用于表示"字段缺失"或"值不存在"。但在真实业务里它们往往语义模糊:一条 JSON 里某个键是缺失(undefined)、显式置空(null)还是真的没有值,调用方很难区分;而直接以T | undefined或T | null | undefined贯穿整个应用,又会让每个使用点都要重复做判空。
Effect 的Option<T>类型恰好用来显式表达"可能存在、也可能不存在":
Option.some(value)表示值存在;Option.none()表示值不存在。
变更记录(.changeset/pre/add-schema-option-from-undefined-nullish.md)宣告在 Schema 模块中新增两个便捷 schema:
Schema: add `OptionFromUndefinedOr` and `OptionFromNullishOr` schemas.它们与既有的OptionFromNullOr构成完整三元组,分别处理undefined、null、以及两者皆可(nullish)三种缺省语义,让"可选字段 →Option"的转换从手工拼接变换变成一行声明。
新增 API 概览
三个"从可选值构造 Option"的 schema 全部定义在 packages/effect/src/Schema.ts(自 3.10.0 起提供),签名与语义如下:
| Schema | 输入(Encoded) | 解码结果(Type) | 说明 |
|---|---|---|---|
OptionFromNullOr<S> | T \| null | Option<T> | null→None,其余 →Some |
OptionFromUndefinedOr<S> | T \| undefined | Option<T> | undefined→None,其余 →Some |
OptionFromNullishOr<S> | T \| null \| undefined | Option<T> | null/undefined→None,其余 →Some |
OptionFromUndefinedOr对应 Schema.ts 中的 OptionFromUndefinedOr 定义,类型签名继承自decodeTo<Option<toType<S>>, UndefinedOr<S>>,并带有"Rebuild"元数据以支持组合重建。OptionFromNullishOr对应 Schema.ts 中的 OptionFromNullishOr 定义,额外接受一个可选配置options.onNoneEncoding: null | undefined,用于指定None在编码回外部表示时的取值。
三者都接收一个普通 schemaS作为值类型,例如Schema.String、Schema.Number、Schema.FiniteFromString等,返回的是"可双向转换"的 schema——既能把外部宽松表示解码为Option,也能把Option编码回外部表示。
解码与编码语义
OptionFromUndefinedOr:undefined与缺失等价
undefined在 JavaScript/TypeScript 中天然表示"未定义",与 JSON 中键缺失高度契合。该 schema 的语义(见 Schema.ts 中 OptionFromUndefinedOr 的 JSDoc):
- 解码:
undefined→Option.none();其他任何值 →Option.some(value)(随后按内部 schema 校验,如"1"→1)。 - 编码:
Option.none()→undefined;Option.some(value)→value。
OptionFromNullishOr:null与undefined一视同仁
很多外部系统(如某些数据库、旧式 API)同时用null和undefined表示"无值"。OptionFromNullishOr将二者统一折叠为None(见 Schema.ts 中 OptionFromNullishOr 的 JSDoc):
- 解码:
null或undefined→Option.none();其余值 →Option.some(value)。 - 编码:
Option.none()→ 由options.onNoneEncoding决定编码为null还是undefined,默认值为undefined;Option.some(value)→value。
提示:若只想处理
null而不想理会undefined,请使用既有的OptionFromNullOr;三个 schema 的边界划分正是为了让"缺省语义"与业务一一对应。
底层实现:decodeTo+ 变换原语
新增 schema 并非从零实现,而是建立在 Schema 既有变换机制之上。以 Schema.ts 中三个函数的实现 为例:
export function OptionFromNullOr<S extends Constraint>(schema: S): OptionFromNullOr<S> { return NullOr(schema).pipe(decodeTo( Option(toType(schema)), SchemaTransformation.optionFromNullOr() )) } export function OptionFromUndefinedOr<S extends Constraint>(schema: S): OptionFromUndefinedOr<S> { return UndefinedOr(schema).pipe(decodeTo( Option(toType(schema)), SchemaTransformation.optionFromUndefinedOr() )) } export function OptionFromNullishOr<S extends Constraint>( schema: S, options?: { onNoneEncoding: null | undefined } ): OptionFromNullishOr<S> { return NullishOr(schema).pipe(decodeTo( Option(toType(schema)), SchemaTransformation.optionFromNullishOr(options) )) }其构成分三层:
宽松输入 schema:
NullOr/UndefinedOr/NullishOr在 Schema.ts 中定义为对原 schema 与Null、Undefined的 Union,先把外部表示"放行"为T | null、T | undefined或T | null | undefined。decodeTo变换:将上述宽松表示转换为内部目标类型Option<toType<S>>。变换原语:真正执行
Option与原始值互转的是 packages/effect/src/SchemaTransformation.ts 中的三个纯函数变换(@since 4.0.0):optionFromNullOr<T>()(SchemaTransformation.ts 定义):decode: Option.fromNullOr、encode: Option.getOrNull;optionFromUndefinedOr<T>()(SchemaTransformation.ts 定义):decode: Option.fromUndefinedOr、encode: Option.getOrUndefined;optionFromNullishOr<T>(options?)(SchemaTransformation.ts 定义):decode: Option.fromNullishOr,encode则根据options?.onNoneEncoding === null选择Option.getOrNull还是Option.getOrUndefined(默认后者)。
从源码结构可以推断,
OptionFromNullishOr的onNoneEncoding参数在底层就是编码分支的选择开关:传null时None编码为null,否则编码为undefined。这些变换"纯且同步"(pure and synchronous),不引入异步或副作用。
这种分层设计带来的直接好处:你可以不依赖高层 schema,直接用手写的Schema.NullOr(Schema.String)+Schema.decodeTo(Schema.Option(Schema.String), SchemaTransformation.optionFromNullOr())组合出等价效果,在自定义、内联场景下更灵活。
实战用法
解析 API 响应中的可选字段
import { Option, Schema } from "effect" // 模拟外部返回:某些字段缺失、某些显式为 null const User = Schema.Struct({ id: Schema.Number, // 外部可能缺 key,也可能给 undefined nickname: Schema.OptionFromUndefinedOr(Schema.String), // 外部可能给 null 或 undefined deletedAt: Schema.OptionFromNullishOr(Schema.DateTimeUtcFromSelf) }) const decode = Schema.decodeUnknownSync(User) decode({ id: 1 }) // nickname: Option.none() decode({ id: 1, nickname: "alice" }) // nickname: Option.some("alice") decode({ id: 1, deletedAt: null }) // deletedAt: Option.none()解码后nickname/deletedAt都是类型安全的Option,下游用Option.map、Option.getOrElse等操作即可安全取值,不必再手工判断undefined/null。
控制None的编码回写
当需要把内部Option重新编码为外部表示时,OptionFromNullishOr的onNoneEncoding决定了None落到哪个值:
import { Option, Schema } from "effect" const schema = Schema.OptionFromNullishOr(Schema.String, { onNoneEncoding: null }) Schema.encodeSync(schema)(Option.some("v")) // => "v" Schema.encodeSync(schema)(Option.none()) // => null若不传onNoneEncoding,则默认编码为undefined:
const schema2 = Schema.OptionFromNullishOr(Schema.String) Schema.encodeSync(schema2)(Option.none()) // => undefined提示:仓库内
package.json中"effect": patch标记表明该变更属于补丁级(非破坏性)更新,已有使用OptionFromNullOr的代码可以平滑升级,无需迁移改动。
测试验证与行为保证
新 API 的行为在 packages/effect/test/schema/Schema.test.ts 中有完整覆盖(自 3001 行起):
OptionFromUndefinedOr(Schema.test.ts#L3001-L3017):- 解码:
undefined→Option.none();"1"→Option.some(1);"a"→ 解码失败(Expected a finite number,说明内部 schema 校验仍然生效); - 编码:
Option.none()→undefined;Option.some(1)→"1"。
- 解码:
OptionFromNullishOr的两种配置(Schema.test.ts#L3019-L3057):{ onNoneEncoding: null }:null/undefined均解码为None;None编码回null;{ onNoneEncoding: undefined }:解码行为相同,None编码回undefined。
测试同时验证了 Arbitrary 生成(asserts.arbitrary().verifyGeneration()),说明这两个 schema 也可以用于基于属性测试与数据生成场景。若你在自己的代码中使用,可以参照该文件里的断言方式验证解码/编码往返(round-trip)一致性。
总结
OptionFromUndefinedOr(schema):处理T | undefined,undefined→None;OptionFromNullishOr(schema, { onNoneEncoding }):处理T | null | undefined,null与undefined统一为None,编码回写可选null或undefined;- 二者与
OptionFromNullOr互补,底层复用 SchemaTransformation.ts 的纯函数变换,实现简洁、可组合; - 新 API 自
3.10.0起可用(见 Schema.ts 中@since 3.10.0标注),属于补丁级变更,可直接升级引入。
需要声明:3.10.0的版本号来自源码中@since标注;由于当前仓库包含预发布 changeset,实际发布版本以官方发布渠道为准。
【免费下载链接】effectBuild production-ready applications in TypeScript项目地址: https://gitcode.com/GitHub_Trending/ef/effect
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考