gpt4 book ai didi

javascript - typescript :嵌套对象的深度键,具有相关类型

转载 作者:行者123 更新时间:2023-12-05 00:25:00 27 4
gpt4 key购买 nike

我正在寻找一种方法来拥有嵌套对象的所有键/值对。
(用于 MongoDB 点符号键/值类型的自动完成)

interface IPerson {
name: string;
age: number;
contact: {
address: string;
visitDate: Date;
}
}
这是我想要实现的目标,使其变为:
type TPerson = {
name: string;
age: number;
contact: { address: string; visitDate: Date; }
"contact.address": string;
"contact.visitDate": Date;
}
我试过的:
在这个 answer ,我可以通过 Leaves<IPerson> 获取 key .
所以它变成了 'name' | 'age' | 'contact.address' | 'contact.visitDate' .
在另一个 answer从@jcalz,我可以得到深度相关的值类型, DeepIndex<IPerson, ...> .
是否可以将它们组合在一起,变成像 TPerson 这样的类型? ?
修改 9/14:用例,需要和不需要:
当我开始这个问题时,我想它可以像 [K in keyof T]: T[K]; 这样简单。 ,有一些巧妙的变换。但是我错了。这是我需要的:
1. 索引签名
所以界面
interface IPerson {
contact: {
address: string;
visitDate: Date;
}[]
}
变成
type TPerson = {
[x: `contact.${number}.address`]: string;
[x: `contact.${number}.visitDate`]: Date;
contact: {
address: string;
visitDate: Date;
}[];
}
无需检查有效 number ,数组/索引签名的性质应该允许任意数量的元素。
2.元组
界面
interface IPerson {
contact: [string, Date]
}
变成
type TPerson = {
[x: `contact.0`]: string;
[x: `contact.1`]: Date;
contact: [string, Date];
}
元组应该是关心有效索引号的那个。
3.只读 readonly应从最终结构中删除属性。
interface IPerson {
readonly _id: string;
age: number;
readonly _created_date: Date;
}
变成
type TPerson = {
age: number;
}
该用例适用于 MongoDB, _id , _created_date数据创建后无法修改。 _id: never在这种情况下不起作用,因为它会阻止 TPerson 的创建.
4.可选
interface IPerson {
contact: {
address: string;
visitDate?: Date;
}[];
}
变成
type TPerson = {
[x: `contact.${number}.address`]: string;
[x: `contact.${number}.visitDate`]?: Date;
contact: {
address: string;
visitDate?: Date;
}[];
}
只需将可选标志带入转换后的结构就足够了。
5. 路口
interface IPerson {
contact: { address: string; } & { visitDate: Date; }
}
变成
type TPerson = {
[x: `contact.address`]: string;
[x: `contact.visitDate`]?: Date;
contact: { address: string; } & { visitDate: Date; }
}
6. 可以将类型指定为异常
界面
interface IPerson {
birth: Date;
}
变成
type TPerson = {
birth: Date;
}
不是
type TPerson = {
age: Date;
"age.toDateString": () => string;
"age.toTimeString": () => string;
"age.toLocaleDateString": {
...
}
我们可以给出一个类型列表作为结束节点。
这是我不需要的:
  • 联盟。它可能太复杂了。
  • 类相关关键字。无需处理关键字,例如: private/abstract 。
  • 其他的我这里就不写了。
  • 最佳答案

    为了实现这个目标,我们需要创建所有允许路径的排列。例如:

    type Structure = {
    user: {
    name: string,
    surname: string
    }
    }

    type BlackMagic<T>= T

    // user.name | user.surname
    type Result=BlackMagic<Structure>
    数组和空元组的问题变得更加有趣。
    元组,具有显式长度的数组,应该以这种方式管理:
    type Structure = {
    user: {
    arr: [1, 2],
    }
    }

    type BlackMagic<T> = T

    // "user.arr" | "user.arr.0" | "user.arr.1"
    type Result = BlackMagic<Structure>
    逻辑很严谨。但是我们如何处理 number[] ?不能保证索引 1存在。
    我决定使用 user.arr.${number} .
    type Structure = {
    user: {
    arr: number[],
    }
    }

    type BlackMagic<T> = T

    // "user.arr" | `user.arr.${number}`
    type Result = BlackMagic<Structure>
    我们还有 1 个问题。空元组。零元素数组 - [] .我们是否需要允许索引?我不知道。我决定使用 -1 .
    type Structure = {
    user: {
    arr: [],
    }
    }

    type BlackMagic<T> = T

    // "user.arr" | "user.arr.-1"
    type Result = BlackMagic<Structure>
    我认为这里最重要的是一些约定。我们也可以使用字符串化的“never”。我认为这取决于OP如何处理它。
    因为我们知道我们需要如何处理不同的情况,我们可以开始我们的实现。在我们继续之前,我们需要定义几个助手。
    type Values<T> = T[keyof T]
    {
    // 1 | "John"
    type _ = Values<{ age: 1, name: 'John' }>
    }

    type IsNever<T> = [T] extends [never] ? true : false;
    {
    type _ = IsNever<never> // true
    type __ = IsNever<true> // false
    }

    type IsTuple<T> =
    (T extends Array<any> ?
    (T['length'] extends number
    ? (number extends T['length']
    ? false
    : true)
    : true)
    : false)
    {
    type _ = IsTuple<[1, 2]> // true
    type __ = IsTuple<number[]> // false
    type ___ = IsTuple<{ length: 2 }> // false
    }

    type IsEmptyTuple<T extends Array<any>> = T['length'] extends 0 ? true : false
    {
    type _ = IsEmptyTuple<[]> // true
    type __ = IsEmptyTuple<[1]> // false
    type ___ = IsEmptyTuple<number[]> // false

    }
    我认为命名和测试是不言自明的。至少我愿意相信:D
    现在,当我们拥有所有的实用程序时,我们可以定义我们的主要实用程序:
    /**
    * If Cache is empty return Prop without dot,
    * to avoid ".user"
    */
    type HandleDot<
    Cache extends string,
    Prop extends string | number
    > =
    Cache extends ''
    ? `${Prop}`
    : `${Cache}.${Prop}`

    /**
    * Simple iteration through object properties
    */
    type HandleObject<Obj, Cache extends string> = {
    [Prop in keyof Obj]:
    // concat previous Cacha and Prop
    | HandleDot<Cache, Prop & string>
    // with next Cache and Prop
    | Path<Obj[Prop], HandleDot<Cache, Prop & string>>
    }[keyof Obj]

    type Path<Obj, Cache extends string = ''> =
    // if Obj is primitive
    (Obj extends PropertyKey
    // return Cache
    ? Cache
    // if Obj is Array (can be array, tuple, empty tuple)
    : (Obj extends Array<unknown>
    // and is tuple
    ? (IsTuple<Obj> extends true
    // and tuple is empty
    ? (IsEmptyTuple<Obj> extends true
    // call recursively Path with `-1` as an allowed index
    ? Path<PropertyKey, HandleDot<Cache, -1>>
    // if tuple is not empty we can handle it as regular object
    : HandleObject<Obj, Cache>)
    // if Obj is regular array call Path with union of all elements
    : Path<Obj[number], HandleDot<Cache, number>>)
    // if Obj is neither Array nor Tuple nor Primitive - treat is as object
    : HandleObject<Obj, Cache>)
    )

    // "user" | "user.arr" | `user.arr.${number}`
    type Test = Extract<Path<Structure>, string>
    有小问题。我们不应该返回最高级别的 Prop ,例如 user .我们需要至少有一个点的路径。
    有两种方法:
  • 提取所有不带点的 Prop
  • 为索引级别提供额外的通用参数。

  • 两个选项很容易实现。
    使用 dot (.) 获取所有 Prop :
    type WithDot<T extends string> = T extends `${string}.${string}` ? T : never
    虽然上面的 util 是可读和可维护的,但第二个有点难。我们需要在 Path 中提供额外的通用参数和 HandleObject .
    这个例子取自其他 question/ article :
    type KeysUnion<T, Cache extends string = '', Level extends any[] = []> =
    T extends PropertyKey ? Cache : {
    [P in keyof T]:
    P extends string
    ? Cache extends ''
    ? KeysUnion<T[P], `${P}`, [...Level, 1]>
    : Level['length'] extends 1 // if it is a higher level - proceed
    ? KeysUnion<T[P], `${Cache}.${P}`, [...Level, 1]>
    : Level['length'] extends 2 // stop on second level
    ? Cache | KeysUnion<T[P], `${Cache}`, [...Level, 1]>
    : never
    : never
    }[keyof T]
    老实说,我认为任何人都不容易读到这篇文章。
    我们还需要实现一件事。我们需要通过计算路径获得一个值。

    type Acc = Record<string, any>

    type ReducerCallback<Accumulator extends Acc, El extends string> =
    El extends keyof Accumulator ? Accumulator[El] : Accumulator

    type Reducer<
    Keys extends string,
    Accumulator extends Acc = {}
    > =
    // Key destructure
    Keys extends `${infer Prop}.${infer Rest}`
    // call Reducer with callback, just like in JS
    ? Reducer<Rest, ReducerCallback<Accumulator, Prop>>
    // this is the last part of path because no dot
    : Keys extends `${infer Last}`
    // call reducer with last part
    ? ReducerCallback<Accumulator, Last>
    : never

    {
    type _ = Reducer<'user.arr', Structure> // []
    type __ = Reducer<'user', Structure> // { arr: [] }
    }
    您可以找到有关使用 Reduce 的更多信息 in my blog .
    整个代码:
    type Structure = {
    user: {
    tuple: [42],
    emptyTuple: [],
    array: { age: number }[]
    }
    }


    type Values<T> = T[keyof T]
    {
    // 1 | "John"
    type _ = Values<{ age: 1, name: 'John' }>
    }

    type IsNever<T> = [T] extends [never] ? true : false;
    {
    type _ = IsNever<never> // true
    type __ = IsNever<true> // false
    }

    type IsTuple<T> =
    (T extends Array<any> ?
    (T['length'] extends number
    ? (number extends T['length']
    ? false
    : true)
    : true)
    : false)
    {
    type _ = IsTuple<[1, 2]> // true
    type __ = IsTuple<number[]> // false
    type ___ = IsTuple<{ length: 2 }> // false
    }

    type IsEmptyTuple<T extends Array<any>> = T['length'] extends 0 ? true : false
    {
    type _ = IsEmptyTuple<[]> // true
    type __ = IsEmptyTuple<[1]> // false
    type ___ = IsEmptyTuple<number[]> // false
    }

    /**
    * If Cache is empty return Prop without dot,
    * to avoid ".user"
    */
    type HandleDot<
    Cache extends string,
    Prop extends string | number
    > =
    Cache extends ''
    ? `${Prop}`
    : `${Cache}.${Prop}`

    /**
    * Simple iteration through object properties
    */
    type HandleObject<Obj, Cache extends string> = {
    [Prop in keyof Obj]:
    // concat previous Cacha and Prop
    | HandleDot<Cache, Prop & string>
    // with next Cache and Prop
    | Path<Obj[Prop], HandleDot<Cache, Prop & string>>
    }[keyof Obj]

    type Path<Obj, Cache extends string = ''> =
    (Obj extends PropertyKey
    // return Cache
    ? Cache
    // if Obj is Array (can be array, tuple, empty tuple)
    : (Obj extends Array<unknown>
    // and is tuple
    ? (IsTuple<Obj> extends true
    // and tuple is empty
    ? (IsEmptyTuple<Obj> extends true
    // call recursively Path with `-1` as an allowed index
    ? Path<PropertyKey, HandleDot<Cache, -1>>
    // if tuple is not empty we can handle it as regular object
    : HandleObject<Obj, Cache>)
    // if Obj is regular array call Path with union of all elements
    : Path<Obj[number], HandleDot<Cache, number>>)
    // if Obj is neither Array nor Tuple nor Primitive - treat is as object
    : HandleObject<Obj, Cache>)
    )

    type WithDot<T extends string> = T extends `${string}.${string}` ? T : never


    // "user" | "user.arr" | `user.arr.${number}`
    type Test = WithDot<Extract<Path<Structure>, string>>



    type Acc = Record<string, any>

    type ReducerCallback<Accumulator extends Acc, El extends string> =
    El extends keyof Accumulator ? Accumulator[El] : El extends '-1' ? never : Accumulator

    type Reducer<
    Keys extends string,
    Accumulator extends Acc = {}
    > =
    // Key destructure
    Keys extends `${infer Prop}.${infer Rest}`
    // call Reducer with callback, just like in JS
    ? Reducer<Rest, ReducerCallback<Accumulator, Prop>>
    // this is the last part of path because no dot
    : Keys extends `${infer Last}`
    // call reducer with last part
    ? ReducerCallback<Accumulator, Last>
    : never

    {
    type _ = Reducer<'user.arr', Structure> // []
    type __ = Reducer<'user', Structure> // { arr: [] }
    }

    type BlackMagic<T> = T & {
    [Prop in WithDot<Extract<Path<T>, string>>]: Reducer<Prop, T>
    }

    type Result = BlackMagic<Structure>
    Playground
    This实现值得考虑

    关于javascript - typescript :嵌套对象的深度键,具有相关类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69126879/

    27 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com