1export type AllObjectPaths<T, Filter = unknown> = T extends ReadonlyArray<infer Y> ? [] | [number, ...AllObjectPaths<Y>]
2 : T extends object ?
3 | {
4 [K in keyof T]-?: [K, ...AllObjectPaths<T[K]>];
5 }[keyof T]
6 | []
7 : [];
8export type GetObjectPath<T, P extends unknown[]> = P extends [infer K extends keyof T, ...infer Rest]
9 ? GetObjectPath<T[K], Rest>
10 : T;
11
12/** Get an object path property */
13export function getPath<T extends object, const P extends AllObjectPaths<T>>(
14 target: T,
15 path: P,
16): { value: GetObjectPath<T, P>; exists: true } | {
17 value: undefined;
18 exists: false;
19} {
20 let current: any = target;
21
22 for (const key of path) {
23 if (current == null || typeof current !== "object") {
24 return { value: undefined, exists: false };
25 }
26
27 if (!(key in current)) {
28 return { value: undefined, exists: false };
29 }
30
31 current = current[key];
32 }
33
34 return { value: current, exists: true };
35}
36
37export function setPath<T extends object, const P extends AllObjectPaths<T>>(
38 target: T,
39 path: P,
40 value: GetObjectPath<T, P>,
41): T {
42 if (path.length === 0) {
43 return value as T;
44 }
45
46 const [firstKey, ...restPath] = path as PropertyKey[];
47
48 if (restPath.length === 0) {
49 // Base case: shallow clone and set the value
50 if (Array.isArray(target)) {
51 const result = [...target];
52 result[firstKey as any] = value;
53 return result as T;
54 } else {
55 return { ...target, [firstKey]: value };
56 }
57 }
58
59 // Recursive case: shallow clone, recursively update nested value
60 const nested = (target as any)[firstKey];
61 const updatedNested = setPath(
62 nested ?? (typeof restPath[0] === "number" ? [] : {}),
63 restPath as any,
64 value,
65 );
66
67 if (Array.isArray(target)) {
68 const result = [...target];
69 result[firstKey as any] = updatedNested;
70 return result as T;
71 } else {
72 return { ...target, [firstKey]: updatedNested };
73 }
74}