Created
August 15, 2024 10:17
-
-
Save atesztoth/2d28e33a139298c5af6e7b21afda8954 to your computer and use it in GitHub Desktop.
TS type for finding string terminated key-paths in objects
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Note: this code was originally written for objects containing translations, that is why | |
// at some places type argument names have references to language related things. | |
export type ObjectKeyPaths<ObjectType extends object, Separator extends string = "."> = { | |
[Key in keyof ObjectType & (string | number)]: ObjectType[Key] extends object | |
? `${Key}` | `${Key}${Separator}${ObjectKeyPaths<ObjectType[Key], Separator>}` | |
: `${Key}`; | |
}[keyof ObjectType & (string | number)]; | |
export type Split<S extends string, D extends string> = | |
// If S is not a string literal, bad luck, return a string[] | |
string extends S ? string[] : | |
S extends '' ? [] : | |
S extends `${infer T}${D}${infer U}` ? [T, ...Split<U, D>] : [S]; | |
export type TraversedType<T, K extends string[]> = K extends [infer Last] | |
? Last extends keyof T | |
? T[Last] | |
: never | |
: K extends [infer First, ...infer Rest] | |
? First extends keyof T | |
? Rest extends string[] | |
? TraversedType<T[First], Rest> | |
: never | |
: never | |
: never; | |
export type TypeTerminatedKeyPaths< | |
T extends object, | |
FT, // final termination type | |
S extends string = "->", | |
KeyPaths extends ObjectKeyPaths<T, S> = ObjectKeyPaths<T, S> | |
> = { | |
[K in KeyPaths]: TraversedType<T, Split<K, S>> extends FT ? K : never; | |
}[KeyPaths]; | |
const translations = { | |
en: { | |
alma: { a: 1, b: "b" }, | |
nested: { objects: { are: { well: { supported: "supported" } } } }, | |
string: { termination: { is: { required: {} } } } | |
}, | |
de: { alma: { a: 1, b: "Apple" } } | |
}; | |
type TR = typeof translations; | |
const KEY_PATH_SEPARATOR = "->"; // would write "as const" here, but no need, this has a literal type, not string | |
const getNestedValue = < | |
Strict extends boolean = false, | |
Lang extends keyof TR = keyof TR, | |
BaseObject extends TR[Lang] = TR[Lang], | |
KP extends TypeTerminatedKeyPaths< | |
BaseObject, | |
string, | |
typeof KEY_PATH_SEPARATOR | |
> = TypeTerminatedKeyPaths<BaseObject, string, typeof KEY_PATH_SEPARATOR> | |
>( | |
language: Lang, | |
keyPath: KP, | |
strict = false as Strict | |
): Strict extends true ? string : string | null => { | |
return {} as any as Strict extends true ? string : string | null; // implementation here is not relevant | |
}; | |
// Play with the arguments! | |
const f = getNestedValue("en", "nested->objects->are->well->supported", true); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment