如果给定的字段是在另一个对象中指定的,有没有办法递归地省略对象中的字段?

我想指定我希望从API调用中接收哪些字段,然后让输入脚本正确地输入它(删除API调用中从未指定的字段).

interface Person {
  head: {
    hairColor: string
    eyeColor: string
  }
}

const person: Person = {
  head: {
    hairColor: 'gray',
    eyeColor: 'green',
  }
}

// Converts all values in an object to boolean (recursively)
type BooleanValues<T> = {
  [K in keyof T]?: T[K] extends object ? BooleanValues<T[K]> : boolean
}

const fieldsToInclude: BooleanValues<Person> = {
  head: {
    hairColor: true,
    // No need to specify 'false' values. Unspecified fields should default to 'false' and be omitted.
  }
}

dropFields(person, fieldsToInclude)

function dropFields<T>(x: T, y: BooleanValues<T>): IncludeFieldsBasedOn<T, typeof y> {
  return // ...
}

你怎么打IncludeFieldsBasedOn<T, U>这个字?

我试过了,但由于输入错误,无法正常工作:

type IncludeFieldsBasedOn<T, O extends BooleanValues<T>> = {
  [K in keyof T]: T[K] extends object ? IncludeFieldsBasedOn<T[K], O[K]> : O[K] extends true ? T[K] : never
}

上面的问题是,它不理解O[K]将与T类型中的值"匹配".

推荐答案

第一个问题是,您将类型赋给变量,这不再生成文字值,这会阻止编译器理解对象的确切形状.使用const assertion可以解决此问题:

const person = {
  head: {
    hairColor: 'gray',
    eyeColor: 'green',
  },
} as const

const fieldsToInclude = {
  head: {
    hairColor: true,
  },
} as const 

但是,这样就不会有类型安全性,因为没有类型,这可以通过使用satisfies运算符来修复:

const person = {
  head: {
    hairColor: 'gray',
    eyeColor: 'green',
  },
} as const satisfies Person;

const fieldsToInclude = {
  head: {
    hairColor: true,
  },
} as const satisfies BooleanValues<Person>;

现在,让我们修改IncludeFieldsBasedOn,第一件事将是从O参数中删除约束,因为我们可以不使用它,并使类型判断更容易:

type IncludeFieldsBasedOn<T, O> = {
  [K in keyof O as K extends keyof T
    ? O[K] extends true
      ? K
      : O[K] extends object
      ? K
      : never
    : never]: K extends keyof T
    ? NonNullable<T[K]> extends object
      ? IncludeFieldsBasedOn<NonNullable<T[K]>, O[K]>
      : T[K]
    : never;
}

不是映射到T,而是映射到O的键,并使用key remapping删除O中的FALSE或不是对象的字段.在值WE中,我们判断K是否是T的键,如果是,则判断它是否是对象.在这种情况下,我们递归地调用该对象属性的IncludeFieldsBasedOn.

请注意,使用内置的NonNullable实用程序类型对于可选字段是必要的,以确保条件正常工作.

用途:

function dropFields<T, K extends BooleanValues<T>>(
  value: T,
  x: K,
): IncludeFieldsBasedOn<T, K> {
  return {} as any; // ...
}

测试:

// const result: IncludeFieldsBasedOn<{
//   readonly head: {
//       readonly hairColor: "gray";
//       readonly eyeColor: "green";
//   };
// }, {
//   readonly head: {
//       readonly hairColor: true;
//   };
// }>
const result = dropFields(person, fieldsToInclude);

结果类型没有显示最终结果,这可以通过使用type-samurai包中的Prettify实用程序类型来修复,从而提高类型的可读性:

type IncludeFieldsBasedOn<T, O> = Prettify<{
  [K in keyof O as K extends keyof T
    ? O[K] extends true
      ? K
      : O[K] extends object
      ? K
      : never
    : never]: K extends keyof T
    ? NonNullable<T[K]> extends object
      ? IncludeFieldsBasedOn<NonNullable<T[K]>, O[K]>
      : T[K]
    : never;
}>

测试:

// const result: {
//   readonly head: {
//       readonly hairColor: "gray";
//   };
// }
const result = dropFields(person, fieldsToInclude);

看上go 不错!

playground

Typescript相关问答推荐

如何在类型脚本中声明对象其键名称不同但类型相同?

为什么缩小封闭内部不适用于属性对象,但适用于声明的变量?

React typescribe Jest调用函数

Redux—Toolkit查询仅在不为空的情况下添加参数

rxjs forkJoin在错误后不会停止后续请求

更新为Angular 17后的可选链运算符&?&问题

类型{}上不存在属性&STORE&A;-MobX&A;REACT&A;TYPE脚本

如何使用泛型自动推断TS中的类型

泛型类型联合将参数转换为Never

如何在LucideAngular 添加自定义图标以及如何使用它们

如何将CSV/TXT文件导入到服务中?

记录键的泛型类型

MatTool提示在角垫工作台中不起作用

从对象类型描述生成类型

为什么我在这个重载函数中需要`as any`?

如何创建由空格连接的多个字符串的类型

将对象的属性转换为正确类型和位置的函数参数

Select 类型的子项

可选通用映射器函数出错

从 npm 切换到 pnpm 后出现Typescript 错误