I encountered a similar issue, but upon closer examination of the proposed solution ExtractPathExpressions
, I identified a small problem.
If you attempt to retrieve the key of an array, you will obtain all the keys of its methods.
type ArrayPath = ExtractPathExpressions<[number, number]>;
// number | "length" | "toString" | "toLocaleString" | "pop" | "push" | "concat" | "join" | "reverse" | "shift" | "slice" | "sort" | "splice" | "unshift" | "indexOf" | "lastIndexOf" | ... 19 more ... | "1"
I modified the solution to cater to arrays and extracted subtypes to enhance code readability. Check out my version of the solution here.
type ArrayPath = PathOf<[number, number]>; // "0" | "1"
Full code:
// retrieves the path for any property in the type
export type PathOf<T> = Extract<keyof Flat<T>, string>;
// generates a flat type from interface or type
export type Flat<T, P extends string = '.'> = {
[K in CustomKey<T> as T[K] extends any[] | readonly any[]
? FlatArrayKey<T[K], K, P>
: T[K] extends AbstractObject
? FlatObjectKey<T[K], K, P>
: K]: unknown;
};
// extracts only those keys that have been specified by us
type CustomKey<T> = Exclude<
keyof T,
symbol | keyof Array<unknown> | keyof number | keyof string
>;
// helper
type AbstractObject = Record<string | number, any>;
// helper to create array key
type FlatArrayKey<A extends any[] | readonly any[], K extends string | number, P extends string> =
| K
| `${K}[${number}]`
| `${K}[${number}]${P}${CustomKey<Flat<A[number]>>}`;
// helper to create object key
type FlatObjectKey<O extends AbstractObject, K extends string | number, P extends string> =
| K
| `${K}${P}${CustomKey<Flat<O>>}`;
Example:
type Post = {
id: number
title: string
author: {
name: string
}
comments: {
text: string,
replies: {
author: {
name: string
}
}[],
responses: readonly { a:boolean }[],
ids: string[],
refs: number[],
accepts: readonly bigint[]
}[]
}
type Paths = PathOf<Post>;
//"id" | "title" | "author" | "comments" | "author.name" | `comments[${number}]` | `comments[${number}].text` | `comments[${number}].replies` | `comments[${number}].responses` | `comments[${number}].ids` | `comments[${number}].refs` | `comments[${number}].accepts` | `comments[${number}].replies[${number}]` | `comments[${number}].replies[${number}].author` | `comments[${number}].replies[${number}].author.name` | ... 4 more ... | `comments[${number}].accepts[${number}]
I hope this information proves helpful!