【问题标题】:How to build a recursive type remapper in TypeScript that remaps optional keys differently如何在 TypeScript 中构建以不同方式重新映射可选键的递归类型重新映射器
【发布时间】:2023-01-15 22:58:41
【问题描述】:

我有一组验证变量类型的实用函数。为了 例如string()non_empty_string()array()non_null_object()等 在。它们都是谓词函数并返回一个boolean值(不是 尽管遵循 is<TypeName>() 命名约定!)。所有实用程序 函数属于 Utility 类型的对象。

interface Utility {
  string: (v: unknown) => v is string;
  number: ...;
  natural_number: ...;
  array: ...;
  non_empty_array: ...;
  ...
  ...
}

type UtilityTypes = keyof Utility;

但是现在我想制作一个验证器函数来验证对象 给定实用方法。所以如果我有一个 User 类型的用户对象,

interface User {
  name: string;
  age: number;
  isStudent?: boolean;
  address: {
    city: string;
    state: string;
    phone?: string;
  }
}

然后我想使用如下模式:

type UserValidatorSchema = {
  readonly name: UtilityTypes;
  readonly age: UtilityTypes;
  readonly "isStudent?": UtilityTypes;
  readonly address: {
    readonly city: UtilityTypes;
    readonly state: UtilityTypes;
    readonly "phone?": UtilityTypes;
  }
}

const userSchema: UserValidatorSchema = {
  name: "non_empty_string",
  age: "natural_number",
  "isStudent?": "boolean";
  address: {
    city: "non_empty_string";
    state: "non_empty_string";
    "phone?": "non_empty_string";
  }
}

所有可选属性都应以“?”结尾字符,以便我的验证器 函数可以将其识别为可选属性。

现在我的问题是有什么方法可以生成UserValidatorSchema 自动从给定的User类型?

【问题讨论】:

    标签: typescript


    【解决方案1】:

    我想出了一个解决方案。

    这是 ValidatorSchema 重映射器:

    type ValidatorSchema<Type extends object> = {
      +readonly [key in keyof Type as ModifyOptionalKey<Type, key>]-?: NonNullable<
        Type[key]
      > extends object
        ? ValidatorSchema<NonNullable<Type[key]>>
        : UtilityTypes;
    };
    
    type ModifyOptionalKey<
      Type,
      Key extends keyof Type
    > = undefined extends Type[Key] ? `${Key & string}?` : Key;
    
    //---------------- Usages ---------------
    interface User {
      name: string;
      age: number;
      isGoldUser?: boolean; 
    }
    
    type UserSchema = ValidatorSchema<User>;
    /*
      UserSchema = {
        readonly name: UtilityTypes;
        readonly age: UtilityTypes;
        readonly "isGoldUser?": UtilityTypes
      } 
    */
    

    解释


    ModifyOptionalKey

    此键重新映射器将任何可选字段的名称重新映射到 &lt;fieldName&gt;?(只是 添加一个“?”字符在末尾)。它使用条件类型和模板 字面上来实现这一点。

    interface User {
      name?: string;
    }
    

    这里名称字段的类型实际上是string | undefined,因为作为 名称字段是可选的,它可能不存在于对象中,因此它的默认值 未定义。所以有了这个

    undefined extends Type[Key] ? `${Key &amp; string}?` : Key;

    声明我们可以确定一个属性是否是可选的。如果它是可选的那么 我们将它转​​换为 &lt;property&gt;?`${Key &amp; string}?` 否则我们只是 返回原来的Key(property)。

    ValidatorSchema

    +readonly [key in keyof Type as ModifyOptionalKey&lt;Type, key&gt;]-?

    1. +readonly 使每个财产readonly
    2. [key in keyof Type as ModifyOptionalKey&lt;Type, key&gt;] 重新映射可选 钥匙
    3. -? 从任何属性中删除可选修饰符,从而使其成为必需的。

      现在我们的密钥重放已经完成,我们需要处理属性类型。一种 我们的验证器模式的属性可以是 UtilityTypes 或另一个 ValidatorSchema。所以如果一个属性是object那么它也应该 成为ValidatorSchema

      我们可以简单地使用条件类型来实现这一点。

      Type[key] extends object ? ValidatorSchema&lt;Type[key]&gt; : UtilityTypes;

      如果 Type[key] 引用一个对象,则生成它的 ValidatorSchema 递归调用:ValidatorSchema&lt;Type[key]&gt;否则如果它是原始的 输入然后返回UtilityTypes

      但是这里有个问题!如果我们的子模式之一是 选修的?例如address字段在下面的User类型!

      interface User {
        name: string;
        address?: {
            city: string;
            state: string;
        }
      }
      

      这里的 address 字段代表 ValidatorSchema&lt;User&gt; 架构及其类型 是:{city: string; state: string} | undefined

      type ReturnType = User["address"] extends object
        ? ValidatorSchema<User["address"]>
        : UtilityTypes
      
      // ReturnType = UtilityTypes
      

      我们得到 UtilityTypes 因为 undefined extends object 返回 false。到 从 User["address"] 的类型中删除 undefined 我们将使用 UtilityTypeNonNullable

      笔记:Typescript 自己的实用程序类型,例如 RequiredRecordPick 等。 不涉及我们的实用程序类型,例如 string()non_null_object() 等。 见上面的问题。

      NonNullable&lt;T&gt; 从任何联合类型中删除 undefinednull。 例如

      type nullableType = undefined | null | string;
      
      type nonNullableType = NonNullable<nullableType>;
      // nonNullableType = string
      

      所以 NonNullable&lt;User["address"]&gt; 会使用类型 {city: string; state: string}

      现在,如果我们将以下语句与 NonNullable 一起使用,则一切正常 预期的。

      NonNullable<Type[key]> extends object
          ? ValidatorSchema<NonNullable<Type[key]>>
          : UtilityTypes;
      

      我从“TypeScript 手册”中了解到所有这些。

    【讨论】:

      猜你喜欢
      • 2019-04-15
      • 1970-01-01
      • 1970-01-01
      • 2014-03-05
      • 2021-09-04
      • 2010-10-05
      • 2018-10-12
      • 2014-05-10
      • 2010-10-13
      相关资源
      最近更新 更多