Skip to content

Instantly share code, notes, and snippets.

@SandeepK1729
Last active October 23, 2025 18:48
Show Gist options
  • Select an option

  • Save SandeepK1729/6c08404c5b23ef4c2e4958b59dc17e14 to your computer and use it in GitHub Desktop.

Select an option

Save SandeepK1729/6c08404c5b23ef4c2e4958b59dc17e14 to your computer and use it in GitHub Desktop.
property methods for nested objects with type safety
/**
* Utility methods for nested object
* read and write operations on objects.
*/
/**
* Path - A recursive type to determine the path of nested properties in an object.
* @template T - The object type to extract paths from.
* @returns A union type of string paths representing the nested properties.
* @example
* type Example = {
* a: {
* b: {
* c: number;
* };
* d: string;
* };
* e: boolean;
* };
*
* type ExamplePaths = Path<Example>;
* // Result: "a" | "a.b" | "a.b.c" | "a.d" | "e"
*/
type Path<T extends Record<string, any>> = T extends Record<
infer K extends string,
infer V
>
? V extends Record<string, any>
? K | `${K}.${Path<V>}`
: K
: never;
/**
* PathValue - A recursive type to determine the value type at a given path in an object.
* @template T - The object type to extract values from.
* @template P - The path type representing the nested properties.
* @returns The type of the value located at the specified path.
* @example
* type Example = {
* a: {
* b: {
* c: number;
* };
* d: string;
* };
* e: boolean;
* };
*
* type ValueAtPathC = PathValue<Example, "a.b.c">; // Result: number
* type ValueAtPathD = PathValue<Example, "a.d">; // Result: string
* type ValueAtPathE = PathValue<Example, "e">; // Result: boolean
*/
type PathValue<
T extends Record<string, any>,
P extends Path<T>
> = P extends `${infer K}.${infer Rest}`
? K extends keyof T
? Rest extends Path<T[K]>
? PathValue<T[K], Rest>
: never
: never
: P extends keyof T
? T[P]
: never;
/**
* get - Retrieves the value at the specified path from the given object.
*
* @param obj an object to retrieve the value
* @param path {@link Path} of the key
* @returns value at the given path
* @template T - The object type.
* @template P - The path type representing the nested properties.
* @example
* const exampleObj = {
* a: {
* b: {
* c: 42,
* },
* d: "hello",
* },
* e: true,
* };
*
* const valueC = get(exampleObj, "a.b.c"); // Result: 42
* const valueD = get(exampleObj, "a.d"); // Result: "hello"
* const valueE = get(exampleObj, "e"); // Result: true
*/
const get = <T extends Record<string, any>, P extends Path<T>>(
obj: T,
path: P
): PathValue<T, P> => {
return path.split(".").reduce((o: any, key: string) => o?.[key], obj);
};
/**
* set - Sets the value at the specified path in the given object.
* @param obj an object to set the value
* @param path {@link Path} of the key
* @param value the value to set
* @returns the updated object
* @template T - The object type.
* @template P - The path type representing the nested properties.
* @example
* const exampleObj = {
* a: {
* b: {
* c: 42,
* },
* d: "hello",
* },
* e: true,
* };
*
* set(exampleObj, "a.b.c", 100);
* // exampleObj is now:
* // {
* // a: {
* // b: {
* // c: 100,
* // },
* // d: "hello",
* // },
* // e: true,
* // }
*/
const set = <T extends Record<string, any>, P extends Path<T>>(
obj: T,
path: P,
value: PathValue<T, P>
): T => {
const keys = path.split(".");
const lastKey = keys.pop();
if (!lastKey) {
return obj;
}
const target = keys.reduce(
(o: any, key: string) => (o[key] = o[key] ?? {}),
obj
);
target[lastKey] = value;
return obj;
};
export type { Path, PathValue };
export { get, set };
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment