TypeScript:将对象属性和嵌套对象属性的类型更改为一个类型。

5

有了这个例子:

interface Event {
  title:string;
  description:string;
  fromDate: Date;
  toDate: Date;
  location: {
    name: string;
    lat: number;
    long: number;
  }
}

使用类似PropertiesToString<Event>的类型,我期望返回这种类型:

{
  title:string;
  description:string;
  fromDate: string;
  toDate: string;
  location: {
    name: string;
    lat: string;
    long: string;
  }
}

问题是如何创建类型 PropertiesToString<T>
我已经成功创建了一些可以使用的代码,但对于嵌套对象则无法正常工作。如果有嵌套对象,它会把对象转换为字符串,而不是修改嵌套对象属性的类型并将其转换为字符串。
这是我的版本,它不能正常处理嵌套对象,因为它会将 位置属性 的类型更改为字符串,而不是将位置本身的类型更改为字符串:
export type RequestBody<T> = {
  [P in keyof T]: string;
};
4个回答

6

ToString类型将给定的类型转换为stringPropertiesToString迭代传递的类型的每个键,并使用ToString将其类型更改为string。您可以使用类型三元运算符在ToString中添加其他要处理的特殊情况。

interface Event1 {
    title: string;
    description: string;
    fromDate: Date;
    toDate: Date;
    location: {
        name: string;
        lat: number;
        long: number;
  }
}

type ToString<T> = T extends Date
    ? string
    : T extends object
    ? PropertiesToString<T>
    : string

type PropertiesToString<T> = {
    [K in keyof T]: ToString<T[K]>
}

type Generated = PropertiesToString<Event1>

type X = PropertiesToString<Event1['location']>

const x: Generated = {
    title: 'lorem',
    description: 'lorem',
    fromDate: 'lorem',
    toDate: 'lorem',
    location: {
        name: 'lorem',
        lat: 'lorem',
        long: 'lorem',
    }
}

演示场


手动检查每种类型似乎很笨重。你需要更多的情况来使它工作(布尔值?日期?等等)。 - Tim
@Tim 嗯,那可能是个问题。你的回答巧妙地处理了这个问题。无论如何,我的回答仍然有帮助,而且似乎更“模块化”。人们可以希望将任何类型更改为 string - Shivam Singla
@Tim,我已经更新了答案。它不再需要手动检查每种类型。如果有任何失败,请告诉我,否则请撤销负评 :) - Shivam Singla
嗨,你救了我的一天!然而,我认为 type X = PropertiesToString<Event1['location']> 部分有点令人困惑,因为它与其余部分没有任何关系。 - TOPKAT

2

你的理解已经非常接近了。然而,你可能需要处理一些边缘情况。在JS中,许多东西都是对象,你可能不想让它们全部变成字符串。因此,你可能需要增加一些逻辑来改进这个问题。但最简单的方法是:

type RecursiveObject<T> = T extends Date ? never : T extends object ? T : never; 
export type StringValues<TModel> = {
    [Key in keyof TModel]: TModel[Key] extends RecursiveObject<TModel[Key]> ? StringValues<TModel[Key]> : string;
};

在你的代码中添加任何特殊情况(数组?其他包装类型?)需要处理。最终我们将不处理类型,并且这将变得更加简单。


1
非常感谢!我更喜欢这个版本,因为它更简单、更清晰。 :) - Octavian Regatun

0

另一种方法可以是:

// Do not control Date type here
type RecursiveObject<T> = T extends Date? never : T extends object ? T : never;
// Change properties (nested, or not) OT (Old Type) to NT (New Type)
type ToNewType<T, NT> = { [K in keyof T]: NT };
type NestedTypeChange<T, OT, NT> = {
    [k in keyof T]: T[k] extends OT ? NT : T[k] extends RecursiveObject<T[k]> ? ToNewType<NestedTypeChange<T[k], OT, NT>, NT> : NT
}

1
我认为有错误,NestedTypeChange 总是将所有类型更改为新类型(NT)。 在行末将 NT 更改为 T[k],以保留没有匹配的旧类型。 - Vitexikora

0
不必使用PropertiesToString<Event>,你可以简单地编写一个这样的函数:
const eventToString = (e: Event) => ({
  ...e,
  formDate: e.fromDate.toString(),
  toDate: e.toDate.toString(),
  location: {
    ...e.location,
    lat: String(e.location.lat),
    long: String(e.location.long)
  }
})

type EventStringed = ReturnType<typeof eventToString>

它有一些优点:
- 它比使用高级的TS功能更简单。 - 你有JS函数来转换事件。 - 你可以更多地访问并改变对象属性的类型,而不仅仅是字符串。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接