-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdoPathValue.ts
113 lines (99 loc) · 3.17 KB
/
doPathValue.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
// forked from https://github.com/g-makarov/dot-path-value
export type Primitive = null | undefined | string | number | boolean | symbol | bigint
type ArrayKey = number
type IsTuple<T extends readonly any[]> = number extends T['length'] ? false : true
type TupleKeys<T extends readonly any[]> = Exclude<keyof T, keyof any[]>
export type PathConcat<TKey extends string | number, TValue> = TValue extends Primitive
? `${TKey}`
: `${TKey}` | `${TKey}.${Path<TValue>}`
export type Path<T> = T extends readonly (infer V)[]
? IsTuple<T> extends true
? {
[K in TupleKeys<T>]-?: PathConcat<K & string, T[K]>;
}[TupleKeys<T>]
: PathConcat<ArrayKey, V>
: {
[K in keyof T]-?: PathConcat<K & string, T[K]>;
}[keyof T]
type ArrayPathConcat<TKey extends string | number, TValue> = TValue extends Primitive
? never
: TValue extends readonly (infer U)[]
? U extends Primitive
? never
: `${TKey}` | `${TKey}.${ArrayPath<TValue>}`
: `${TKey}.${ArrayPath<TValue>}`
export type ArrayPath<T> = T extends readonly (infer V)[]
? IsTuple<T> extends true
? {
[K in TupleKeys<T>]-?: ArrayPathConcat<K & string, T[K]>;
}[TupleKeys<T>]
: ArrayPathConcat<ArrayKey, V>
: {
[K in keyof T]-?: ArrayPathConcat<K & string, T[K]>;
}[keyof T]
export type PathValue<T, TPath extends Path<T> | ArrayPath<T>> = T extends any
? TPath extends `${infer K}.${infer R}`
? K extends keyof T
? R extends Path<T[K]>
? undefined extends T[K]
? PathValue<T[K], R> | undefined
: PathValue<T[K], R>
: never
: K extends `${ArrayKey}`
? T extends readonly (infer V)[]
? PathValue<V, R & Path<V>>
: never
: never
: TPath extends keyof T
? T[TPath]
: TPath extends `${ArrayKey}`
? T extends readonly (infer V)[]
? V
: never
: never
: never
/**
* It takes an object and a path, and returns the value at that path, type safe.
* @param {T} obj - The object to get the value from.
* @param {TPath} path - The path to the value you want to get.
* @returns The value of the path in the object.
* @forked from https://github.com/g-makarov/dot-path-value
* @example
* ```ts
* const obj = {
* a: {
* b: {
* c: [
* { d: 1 }
* ]
* },
* }
* getByPath(obj, 'a.b.c[0].d') // 1
* getByPath(obj, 'a.b.c.0') // { d: 1 }
* ```
* @linkcode https://github.com/GreatAuk/utopia-utils/blob/main/packages/core/src/doPathValue.ts
*/
export function getByPath<T extends Record<string, any>, TPath extends Path<T>>(
obj: T,
path: TPath,
): PathValue<T, TPath> {
return path.split('.').reduce((acc, key) => acc?.[key], obj) as PathValue<T, TPath>
}
export function setByPath<T extends Record<string, any>, TPath extends Path<T>>(
obj: T,
path: TPath,
value: PathValue<T, TPath>,
) {
const segments = path.split('.') as TPath[]
const lastKey = segments.pop()
let target: T = obj
for (let i = 0; i < segments.length; i++) {
const key = segments[i] as TPath
if (!(key in target))
target[key] = {} as PathValue<T, TPath>
target = target[key]
}
if (lastKey)
target[lastKey] = value
return obj
}