首页
/ TypeBox 中高级类型映射的实现与解析

TypeBox 中高级类型映射的实现与解析

2025-06-07 05:15:05作者:凤尚柏Louis

概述

在使用 TypeBox 进行复杂类型映射时,开发者可能会遇到静态类型推断不符合预期的情况。本文将深入探讨如何正确实现从源类型到目标类型的转换,特别是在需要重命名属性并生成自定义模式的情况下。

问题背景

当我们需要从一个包含多个模式的对象生成对应的验证模式时,通常会遇到以下需求:

  1. 根据源对象中的类型描述符生成不同的验证规则
  2. 对属性名进行转换(如添加"_filter"后缀)
  3. 保持类型安全的同时实现运行时验证

解决方案架构

1. 定义源类型

首先需要明确定义源数据结构:

type Descriptor = Readonly<{ type: 'number' | 'text', display: string }>
type Schema = Readonly<{ [key: string]: Descriptor }>
type Schemas = Readonly<{ [key: string]: Schema }>

2. 类型转换器实现

实现从描述符到验证模式的转换需要分层处理:

单个描述符转换

type TFromDescriptor<T extends Schema, K extends PropertyKey> = 
  K extends keyof T 
    ? Record<`${K & string}_filter`, 
        T[K]['type'] extends 'number' ? TOptional<TNumber> :
        T[K]['type'] extends 'text' ? TOptional<TString> :
        TNever> 
    : {}

对应的运行时实现:

function FromDescriptor<T extends Schema, K extends keyof T>(
  schema: T, 
  key: K
): TFromDescriptor<T, K> {
  return {
    [`${key as string}_filter`]: 
      schema[key]['type'] === 'number' ? Optional(Number()) :
      schema[key]['type'] === 'text' ? Optional(String()) :
      Never()
  } as never
}

模式级转换

通过递归处理模式中的所有键:

type TFromSchemaReduce<T extends Schema, K extends PropertyKey[], Acc = {}> = 
  K extends [infer L extends PropertyKey, ...infer R extends PropertyKey[]]
    ? TFromSchemaReduce<T, R, Acc & TFromDescriptor<T, L>>
    : TObject<Evaluate<Acc>>

运行时实现使用reduce累积属性:

function FromSchemaReduce<T extends Schema, K extends PropertyKey[]>(
  schema: T, 
  keys: K
): TFromSchemaReduce<T, K> {
  const properties = keys.reduce((Acc, L) => {
    return { ...Acc, ...FromDescriptor(schema, L as keyof T) }
  }, {} as TProperties)
  return Type.Object(properties, { additionalProperties: false }) as never
}

完整模式转换

最终将整个模式对象转换为验证模式集合:

type TFromSchemas<T extends Schemas> = Evaluate<{
  -readonly [K in keyof T]: TFromSchema<T[K]>
}>

实际应用示例

定义源数据:

const Mapped = FromSchemas({
  foo: { 
    text1: { type: "text", display: "Text 1" }, 
    number1: { type: "number", display: "Number 1" } 
  },
  bar: { 
    text2: { type: "text", display: "Text 2" }, 
    number2: { type: "number", display: "Number 2" } 
  }
} as const)

获取验证器和静态类型:

const fooSchema = Mapped["foo"]
type FooSchema = Static<typeof fooSchema>

const barValidator = TypeCompiler.Compile(Mapped["bar"])

关键点总结

  1. 分层处理:将复杂类型转换分解为描述符→模式→模式集合三个层次
  2. 类型安全:确保运行时实现与静态类型声明完全匹配
  3. 递归处理:使用递归类型处理可变长度的键集合
  4. 类型求值:使用Evaluate工具类型简化复杂交叉类型的显示

通过这种结构化的方法,开发者可以在TypeBox中实现复杂的类型映射,同时保持类型安全和运行时验证的一致性。

登录后查看全文
热门项目推荐
相关项目推荐