type GetPath<T extends object, Path extends string, Join extends string, K extends string | number> = T extends readonly unknown[]
? `${Path}${Join}${K}` | `${Path}${Join}[${K}]` | `${Path}[${K}]`
: `${Path}${Join}${K}`
type ObjectKeyPaths<T extends object, Path extends string = '', Join extends string = ''> = {
[K in keyof T & (string | number) as T extends readonly unknown[] ? K extends number ? K : never : K]:
T[K] extends object
? GetPath<T, Path, Join, K> | ObjectKeyPaths<T[K], GetPath<T, Path, Join, K>, '.'>
: GetPath<T, Path, Join, K>
} extends infer Obj ? Obj[keyof Obj] : never
Solution by 2083335157 #35199
This one works on recursive/circular objects. For arrays, it only accepts .0
, for cleaner IDE suggestions, instead of things like .[$number]
, but you can change it yourself if you want to use that notation.
type Digit = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
type NextDigit = [1, 2, 3, 4, 5, 6, 7, 'STOP']
type Inc<T> = T extends Digit ? NextDigit[T] : 'STOP'
type StringOrNumKeys<TObj> = TObj extends unknown[] ? 0 : keyof TObj & string
type NestedPath<TValue, Prefix extends string, TValueNestedChild, TDepth> = TValue extends object
? `${Prefix}.${TDepth extends 'STOP' ? string : NestedFieldPaths<TValue, TValueNestedChild, TDepth>}`
: never
type GetValue<T, K extends string | number> = T extends unknown[]
? K extends number
? T[K]
: never
: K extends keyof T
? T[K]
: never
type NestedFieldPaths<TData = any, TValue = any, TDepth = 0> = {
[TKey in StringOrNumKeys<TData>]:
| (GetValue<TData, TKey> extends TValue ? `${TKey}` : never)
| NestedPath<GetValue<TData, TKey>, `${TKey}`, TValue, Inc<TDepth>>
}[StringOrNumKeys<TData>]
export type ObjectKeyPaths <TData = any> = TData extends any ? NestedFieldPaths<TData, any, 1> : never
Credits: I based the code on the article Supporting Circularly Referenced Mapped Types in Typescript, by Stephen Cooper
>>> TS Playground <<<
Solution by mauriciabad #34401
type ObjectKeyPaths<
T extends object,
K extends keyof T = keyof T
> = K extends any
? T[K] extends any[]
? T[K][number] extends object
?
| K
| `${K & string}${
| `.${ArrayIndex<T[K]>}.`
| `[${ArrayIndex<T[K]>}]`}${ObjectKeyPaths<T[K][number]> &
string}`
: K | `${K & string}${`.${T[K][number]}` | `[${T[K][number]}]`}`
: T[K] extends object
? K | `${K & string}.${ObjectKeyPaths<T[K]> & string}`
: K
: never;
type ArrayIndex<T extends any[]> = {
[P in keyof T]: P
}[number]
Solution by Vampirelee #32650
type ObjectKeyPaths<
T,
D extends string = "",
K extends keyof T = keyof T
> = K extends string | number
? `${`${D}${K}` | (T extends unknown[] ? `[${K}]` | `${D}[${K}]` : never)}${
| ""
| (T[K] extends object ? ObjectKeyPaths<T[K], "."> : "")}`
: never;
Solution by vangie #32311
type ObjectKeyPaths<T extends Record<string, any>, K extends keyof T = keyof T> = K extends keyof T
? K extends string | number
? T[K] extends Record<string, any>
? T[K] extends unknown[]
? ${K}
| ${K}[${number}]
| ${K}.[${number}]
| ${K}.${ObjectKeyPaths<T[K]>}
: ${K}
| ${K}.${ObjectKeyPaths<T[K]>}
: ${K}
: never
: never
Solution by Czhongda #30857
// your answers
type AddPath<Keys, Path> = Keys extends string | number ? Path extends string | number ? Keys extends number ? `${Path}.${Keys}` | `${Path}[${Keys}]` | `${Path}.[${Keys}]` : `${Path}.${Keys}` : never : never
type GetObject<T extends object> = {
[Key in keyof T as T[Key] extends object ? AddPath<keyof GetObject<T[Key]>, Key> | Key : Key]: 1
}
type ObjectKeyPaths<T extends object> = keyof GetObject<T>
Solution by 437204933 #29718
/**是否为readonly数组 */
type IsReadonlyArray<T extends readonly any[]> = [T[`length`] & -1] extends [never] ? true : false;
/**固定将keyof输出成string */
type ToKeyString<K extends PropertyKey> = K extends string | number ? `${K}` : ``;
/**取[index].type的字符串形式 */
type ToIndexTypeString<S extends readonly any[]> = ToKeyString<keyof { [P in keyof S as
IsReadonlyArray<S> extends true ? //应付as const情景
(P extends `${number}` ?
`${(`${`` | `.`}[${ToKeyString<P>}]`) | `.${ToKeyString<P>}`}${S[P] extends object ? `.${ObjectKeyPaths<S[P]>}` : ``}` : //[0] .[0] .0 (.type)
``)
:
(`${(`${`` | `.`}[${number}]`) | `.${number}`}${S[number] extends object ? `.${ObjectKeyPaths<S[number]>}` : ``}`) //[${number}] .[${number}] .${number} (.type)
]: any }>;
//数组情况分两种:
//1. ref{} as const 时,数组为readonly,索引数可以确定,例:person.books[1]、person.pets.0.type
//2. ref{} 不 as const 时,数组不为readonly,索引数不可确定,因此转化为${number},例:person.books[${number}]、person.pets.${number}.type
//此处题目为第二种情景
type ObjectKeyPaths<T extends object> = ToKeyString<
keyof T | keyof { [P in keyof T as
(P extends string | number ?
T[P] extends readonly unknown[] ? `${P}${ToIndexTypeString<T[P]>}` : //数组
(T[P] extends object ? `${P}.${ObjectKeyPaths<T[P]>}` : P) : //结构体
P)]: T[P] }
>;
Solution by E-uler #25015
type JoinPrefix<Prefix extends string, Path extends string> = Prefix extends '' ? Path : `${Prefix}.${Path}`
type Num2Array<N extends number, T extends unknown[] = []> = T['length'] extends N ? T : Num2Array<N, [...T, unknown]>
type AddOne<N extends number> = [...Num2Array<N>, unknown]['length']
/**
* 注意: 数组类型是object的子类型,所以在高级类型ObjectKeyPaths中要先判断数组的情况
*/
type ArrayIsExtendsObject = [1, 2, 3] extends object ? true : false
type ObjectKeyPaths<T extends object, Prefix extends string = '', Key extends keyof T = keyof T> = Key extends Key ?
JoinPrefix<Prefix, Key & string> | (T[Key] extends any[] ? ArrayKeyPath<T[Key], never, JoinPrefix<Prefix, Key & string>> :
T[Key] extends object ? ObjectKeyPaths<T[Key], JoinPrefix<Prefix, Key & string>> : never) : never
type ArrayKeyPath<T extends any[], Res = never, Prefix extends string = '', Index extends number = 0> = T extends [infer Cur, ...infer Rest] ?
ArrayKeyPath<Rest, Res | JoinPrefix<Prefix, `${Index}`> |
(Cur extends any[] ? ArrayKeyPath<Cur, never, JoinPrefix<Prefix, `${Index}`>> :
Cur extends object ? ObjectKeyPaths<Cur, JoinPrefix<Prefix, `${Index}`>> : never)
, Prefix, AddOne<Index> & number> : Res
Solution by zhuizhui0429 #24990
type ConcatPath<P extends string | number, K extends string | number> =
[P] extends [never]
? K
: `${P}.${K}` | ([K] extends [number] ? `${P}[${K}]` | `${P}.[${K}]` : never)
type ObjectKeyPaths<
T,
P extends string | number = never,
K extends keyof T = keyof T,
> =
T extends Record<string, unknown>[]
? ConcatPath<P, number> | ObjectKeyPaths<T[number], ConcatPath<P, number>>
: T extends unknown[]
? ConcatPath<P, number>
: T extends Record<string, unknown>
? K extends string | number
? ConcatPath<P, K> | ObjectKeyPaths<T[K], ConcatPath<P, K>>
: never
: never
Solution by drylint #23300
// your answers
type BuildArray<
Length extends number,
Ele = unknown,
Arr extends unknown[] = []
> = Arr['length'] extends Length
? Arr
: BuildArray<Length, Ele, [...Arr, Ele]>;
type Add<Num1 extends number, Num2 extends number> =
[...BuildArray<Num1>, ...BuildArray<Num2>]['length'];
type GetObjectKeyPathsByArr<T extends any[], Index extends number = 0> =
T extends [infer F, ...infer L]
? (F extends Record<PropertyKey, any> ? `[${Index}]` | `[${Index}].${keyof F & string}`
| GetObjectKeyPathsByArr<L, Add<Index, 1>>
: `[${Index}]` | GetObjectKeyPathsByArr<L, Add<Index, 1>>) : `${T[number]}_`
type ObjectKeyPaths<T extends object> = {
[key in keyof T]:
T[key] extends any[] ? key | `${key & string}.${GetObjectKeyPathsByArr<T[key], 0> & string}` :
(T[key] extends Record<PropertyKey, any> ? key | `${key & string}.${ObjectKeyPaths<T[key]> & string}` :
key)
}[keyof T]
Solution by xinxinhe1810 #22951
Considering that so far the best solution was this, current one is very close to it, but a little bit more compact
type Keys<O, IsTop, K extends string | number> =
IsTop extends true
? K | (O extends unknown[] ? `[${K}]` : never)
: `.${K}` | (O extends unknown[] ? `[${K}]` | `.[${K}]` : never)
type ObjectKeyPaths<T, IsTop = true, K extends keyof T = keyof T> =
K extends string | number
? `${Keys<T, IsTop, K>}${'' | (T[K] extends object ? ObjectKeyPaths<T[K], false> : '')}`
: never
Solution by Alexsey #22298
// your answers
type ObjectKeyPaths<
T extends object,
R extends string = '',
K extends keyof T = keyof T
> = K extends K
? T[K] extends Record<keyof any, any>
?
| (R extends ''
? K
: K extends number
? `${R}.${K}` | `${R}.[${K}]` | `${R}[${K}]`
: `${R}.${K & string}`)
| ObjectKeyPaths<
T[K],
R extends ''
? `${K & (string | number)}`
: K extends number
? `${R}.${K}` | `${R}.[${K}]` | `${R}[${K}]`
: `${R}.${K & string}`
>
: R extends ''
? K
: K extends number
? `${R}.${K}` | `${R}.[${K}]` | `${R}[${K}]`
: `${R}.${K & string}`
: never
Solution by TKBnice #22291
type ObjectKeyPaths<T, PropertyBasePath extends string = '', Key extends keyof T = keyof T> = Key extends
| string
| number
?
| `${PropertyBasePath}${Key}`
| (T extends any[] ? `${PropertyBasePath}[${Key}]` : never)
| (T[Key] extends object
? ObjectKeyPaths<T[Key], `${PropertyBasePath}${Key}.`>
: never)
: never;
Solution by zqiangxu #22088
// 这该死的题浪费了我两天时间
// ArrIndex ... 题目里不是元组,所以没法指定下标.直接 number 完事, 然后 T[number] 遍历它的类型,
type ArrIndex = `[${number}]` | `.[${number}]` | `.${number}`
type ObjectKeyPaths<
T extends object,
P extends string = '',
K extends keyof T = keyof T
> = K extends keyof T
? `${P}${K & string}`
| (
T[K] extends any[]
? `${P}${K & string}${ArrIndex}`
// T[K][number] 这样就能把 动态数组里的类型全给遍历出来了,
| ObjectKeyPaths<T[K][number], `${P}${K & string}${ArrIndex}.`>
: T[K] extends object
? ObjectKeyPaths<T[K], `${P}${K & string}.`>
: never
)
: never
Solution by goddnsgit #22011
There is a bug in current playground, we need to add as const
to the ref, otherwise the typeof ref
will result in incorrect type.
type WithPrefix<K extends string, P extends string = ''> = P extends '' ? K : `${P}.${K}`
// see https://github.com/microsoft/TypeScript/issues/32917 idea 2
// with some modification to adapt them to this challenge
type ArrayKeys = Exclude<keyof [], symbol | number>
type Indices<T> = Exclude<keyof T, ArrayKeys | symbol | number>;
type Tuple2Object<T extends readonly unknown[]> = {
[P in Indices<T>]: T[P]
}
type ExtraTupleKeysOfIndices<T extends readonly unknown[], P extends string, _I = Indices<T>> =
_I extends string
? `${P}[${_I}]` | `${P}.[${_I}]`
: never
type ExtraTupleKeys<T extends readonly unknown[], P extends string = ''> = WithPrefix<ArrayKeys, P> | ExtraTupleKeysOfIndices<T, P>
type ObjectKeyPaths<T extends Record<string, unknown>, P extends string = '', _K extends Extract<keyof T, string> = Extract<keyof T, string>> =
_K extends unknown
? T[_K] extends Record<string, unknown>
? WithPrefix<_K, P> | ObjectKeyPaths<T[_K], WithPrefix<_K, P>>
: T[_K] extends readonly unknown[]
? WithPrefix<_K, P> | ExtraTupleKeys<T[_K], WithPrefix<_K, P>> | ObjectKeyPaths<Tuple2Object<T[_K]>, WithPrefix<_K, P>>
: WithPrefix<_K, P>
: never
The core part is not hard, but this challenge has many edge case need to be handled carefully.
For me, the most challenging part is how to get the indices of a tuple. I am inspired by this issue's idea 2, which gives me the following type utility:
type Indices<T> = Exclude<keyof T, keyof []>;
Solution by zhaoyao91 #21998
type ExcludeArrayKey<T> = Exclude<keyof T & string, keyof Array<any> & string>;
type ObjectKeyPaths1<
T extends object,
G extends keyof T = keyof T
> = G extends string
? T[G] extends readonly any[]
?
| `${G}${
| `[${ExcludeArrayKey<T[G]>}]`
| `.${ObjectKeyPaths1<T[G]>}`
| `.[${ObjectKeyPaths1<T[G]>}]`}`
// | `${G}.${keyof T[G] & string}`
: T[G] extends Record<string, any>
? `${G}${`.${ObjectKeyPaths1<T[G]>}`}`
| `${G}.${keyof T[G] & string}`
: G
: never;
type ObjectKeyPaths<T extends object> = ObjectKeyPaths1<T> | keyof T;
Solution by so11y #21292
It seems similar to #18285
type _Prefix<K, Root extends boolean = true> = K extends number
? `.${ K }` | `[${ K }]` | `.[${ K }]`
: K extends string
? `${ Root extends true ? '' : '.' }${ K }`
: never
type ObjectKeyPaths<
T extends object,
Root extends boolean = true,
_KOT extends keyof T = keyof T,
> = _KOT extends unknown
? _Prefix<_KOT, Root> | (
T[_KOT] extends object
? `${ _Prefix<_KOT, Root> extends string ? _Prefix<_KOT, Root> : '' }${ ObjectKeyPaths<T[_KOT], false> }`
: never
)
: never
Solution by lvjiaxuan #20576
type ObjectKeyPaths<T extends unknown, K extends keyof T = keyof T> = T extends
| Record<any, unknown>
| unknown[]
? K extends keyof T & (string | number)
? `${
// if T is an array, K could be in []
T extends unknown[] ? `[${K}]` | K : K
}${
| ''
| `${
// if T[K] is an array, there could be a empty string between T and T[K]
T[K] extends unknown[] ? '' | '.' : '.'
}${ObjectKeyPaths<T[K]>}`}`
: never
: never
Solution by theoolee #19758
// ❌ todo:这个还没搞懂
type GetPath<K extends PropertyKey & (string | number), Prefix extends string = ''> = [Prefix] extends [never]
? `${K}`
: K extends number
? `${Prefix}.${K}` | `${Prefix}[${K}]` | `${Prefix}.[${K}]`
: `${Prefix}.${K}`;
type ObjectKeyPaths<T extends object, Result extends string = never> =
| Result
| {
[p in keyof T & (string | number)]: T[p] extends object ? ObjectKeyPaths<T[p], GetPath<p, Result>> : GetPath<p, Result>;
}[keyof T & (string | number)];
Solution by CaoXueLiang #19509
// your answers
type GetPath<K extends PropertyKey & (string | number), Prefix extends string = ''> = [Prefix] extends [never]
? `${K}`
: K extends number
? `${Prefix}.${K}` | `${Prefix}[${K}]` | `${Prefix}.[${K}]`
: `${Prefix}.${K}`
type ObjectKeyPaths<T extends object, Result extends string = never> = Result | {
[P in keyof T & (string | number)]: T[P] extends object
? ObjectKeyPaths<T[P], GetPath<P, Result>>
: GetPath<P, Result>
}[keyof T & (string | number)]
Solution by humandetail #16505
// your answerstype ObjectKeyPaths<
T,
O extends any =T ,
Keys extends (string|number)&keyof T = Exclude<(string|number)&keyof T,string&keyof []>> =
T extends object
? Keys extends keyof O
? `${Keys}`
| `${Keys}${ObjectKeyPaths<T[Keys],O>}`
: Keys extends string
? `.${Keys}`
| `.${Keys}${ObjectKeyPaths<T[Keys],O>}`
//: `.${Keys}`
: `.${Keys}`
| `.${Keys}${ObjectKeyPaths<T[Keys],O>}`
| `.[${Keys}]`
| `.[${Keys}]${ObjectKeyPaths<T[Keys],O>}`
| `[${Keys}]`
| `[${Keys}]${ObjectKeyPaths<T[Keys],O>}`
: never ;
Solution by justBadProgrammer #16033
interface SomeObject {
[key: string]: unknown;
}
type SomeArray = unknown[];
type PathString<Path extends string | number> = Path extends number
? Path | `[${Path}]`
: Path;
type SegmentPath<
Path extends string | number,
BasePath extends string = ""
> = BasePath extends ""
? PathString<Path>
: Path extends number
? `${BasePath}.${PathString<Path>}` | `${BasePath}${PathString<Path>}`
: `${BasePath}.${PathString<Path>}`;
type ArrayKeyPaths<
Model extends unknown[],
BasePath extends string = ""
> = Model extends []
? never
:
| SegmentPath<number, BasePath>
| (Model[number] extends SomeObject
? ObjectKeyPaths<Model[number], SegmentPath<number, BasePath>>
: never)
| (Model[number] extends SomeArray
? ArrayKeyPaths<Model[number], SegmentPath<number, BasePath>>
: never);
type ObjectKeyPaths<
Model extends SomeObject,
BasePath extends string = "",
Keys extends keyof Model = keyof Model
> = Keys extends string | number
?
| SegmentPath<Keys, BasePath>
| (Model[Keys] extends SomeObject
? ObjectKeyPaths<Model[Keys], SegmentPath<Keys, BasePath>>
: never)
| (Model[Keys] extends SomeArray
? ArrayKeyPaths<Model[Keys], SegmentPath<Keys, BasePath>>
: never)
: never;
Solution by schemelev #15114
type ObjectKeyPaths<T extends object, P extends string = never> =
P | {
[K in keyof T & (string | number)]:
T[K] extends object ? (
ObjectKeyPaths<T[K], AddPrefix<P, K>>
) : AddPrefix<P, K>
}[keyof T & (string | number)]
type AddPrefix<P extends string, Path extends & string | number> =
[P] extends [never] ? (
`${Path}`
) : Path extends number ? (
`${P}.${Path}` | `${P}[${Path}]` | `${P}.[${Path}]`
) : `${P}.${Path}`
Solution by teamchong #11753
type IsNumber<T> = T extends number ? `[${T}]` | `.[${T}]` : never
type ObjectKeyPaths<
T extends object,
Flag extends boolean = false,
K extends keyof T = keyof T
> = K extends string | number
? (Flag extends true ? `.${K}` | IsNumber<K>: `${K}`) |
(
T[K] extends Record<string, any>
? `${Flag extends true ? `.${K}` | IsNumber<K> : `${K}`}${ObjectKeyPaths<T[K], true>}`
: never
)
: never;
Solution by ProsperBao #10903
type ObjectKeyPaths<
T extends object,
Res extends string = "",
K extends keyof T = keyof T
> = K extends K
? T[K] extends Record<keyof any, any>
?
| (Res extends ""
? K
: K extends number
? `${Res}.${K}` | `${Res}.[${K}]` | `${Res}[${K}]`
: `${Res}.${K & string}`)
| ObjectKeyPaths<
T[K],
Res extends ""
? `${K & (string | number)}`
: K extends number
? `${Res}.${K}` | `${Res}.[${K}]` | `${Res}[${K}]`
: `${Res}.${K & string}`
>
: Res extends ""
? K
: K extends number
? `${Res}.${K}` | `${Res}.[${K}]` | `${Res}[${K}]`
: `${Res}.${K & string}`
: never;
Solution by DanielLin0516 #10803
type ObjectKeyPaths<T extends object, PreStr extends string = ''> = {
[key in keyof T]:
([PreStr, ''] extends ['', PreStr] ? key : `${PreStr}.${key & string}`) extends infer Key
? T[key] extends ReadonlyArray<unknown> ? ArrayKeyPaths<T[key], Key & string> | Key
: T[key] extends Record<PropertyKey, unknown>
? ObjectKeyPaths<T[key], Key & string> | Key
: Key
: never
}[keyof T];
type ArrayKeyPaths<T extends ReadonlyArray<unknown>, PreStr extends string = ''> = {
[key in keyof T]: key extends string
?([PreStr, ''] extends ['', PreStr] ? key : `${PreStr}.${key}` | `${PreStr}[${key}]` | `${PreStr}.[${key}]`) extends infer Key
? T[key] extends ReadonlyArray<unknown> ? ArrayKeyPaths<T[key], Key & string> | Key
: T[key] extends Record<PropertyKey, unknown>
? ObjectKeyPaths<T[key], Key & string> | Key
: Key
: never
: never
}[number];
Solution by heretic-G #8612
type Paths<T, LAST extends string='', R extends keyof T = keyof T>
= T extends Record<string|number, any>
? `${LAST}.${R & string}` | (R extends any ? Paths<T[R], `${LAST}.${R & string}`> : never)
: never
type ObjectKeyPaths<T> = `$${Paths<T>}`
Solution by lishion #8503
type ObjectKeyPaths<T extends object, Res extends string = '', K extends keyof T = keyof T>
=
(K extends K ?
T[K] extends Record<keyof any, any> ?
(Res extends '' ? K : K extends number ? `${Res}.${K}` | `${Res}.[${K}]` | `${Res}[${K}]`
: `${Res}.${K & (string)}`) | ObjectKeyPaths<T[K], Res extends '' ? `${K & (string | number)}` : K extends number ? `${Res}.${K}` | `${Res}.[${K}]` | `${Res}[${K}]` : `${Res}.${K & (string)}`> : Res extends '' ? K : K extends number ? `${Res}.${K}` | `${Res}.[${K}]` | `${Res}[${K}]` : `${Res}.${K & (string)}`
: never)
Solution by EGyoung #8232
// your answers
type ObjectKeyPaths<T, P extends string = "", K extends string | number = Extract<keyof T, T extends any[]? number: string>> =
object extends T ? string :
T extends any[] ? `${P}${K}` | `${P}[${K}]` | `[${K}]` | `${P}${SubKeys<T, K>}` :
T extends object ? `${P}${K}`| `${P}${SubKeys<T, K>}` :
never;
Solution by venusliang #8136
type GenNode<K extends string | number,IsRoot extends boolean> = IsRoot extends true? `${K}`: `.${K}` | (K extends number? `[${K}]` | `.[${K}]`:never)
type ObjectKeyPaths<
T extends object,
IsRoot extends boolean = true,
K extends keyof T = keyof T
> =
K extends string | number ?
GenNode<K,IsRoot> | (T[K] extends object? `${GenNode<K,IsRoot>}${ObjectKeyPaths<T[K],false>}`:never)
:never;
Solution by jiangshanmeta #7939