Skip to content

Instantly share code, notes, and snippets.

@atesztoth
Created August 15, 2024 10:17
Show Gist options
  • Save atesztoth/2d28e33a139298c5af6e7b21afda8954 to your computer and use it in GitHub Desktop.
Save atesztoth/2d28e33a139298c5af6e7b21afda8954 to your computer and use it in GitHub Desktop.
TS type for finding string terminated key-paths in objects
// 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