-
Notifications
You must be signed in to change notification settings - Fork 2
/
flatten.ts
64 lines (55 loc) · 1.8 KB
/
flatten.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
/**
* ### flatten(object)
*
* Flattens a nested object into an object with dot notation keys.
*
* ```js
* flocky.flatten({ a: { b: 1, c: 2 }, d: { e: { f: 3 } } })
* // -> { 'a.b': 1, 'a.c': 2, 'd.e.f': 3 }
* ```
*/
type Entry = { key: string; value: unknown; optional: boolean }
type Explode<T> = _Explode<T extends ReadonlyArray<unknown> ? { '0': T[number] } : T>
type _Explode<T> = T extends object
? {
[K in keyof T]-?: K extends string
? Explode<T[K]> extends infer E
? E extends Entry
? {
key: `${K}${E['key'] extends '' ? '' : '.'}${E['key']}`
value: E['value']
optional: E['key'] extends ''
? object extends Pick<T, K>
? true
: false
: E['optional']
}
: never
: never
: never
}[keyof T]
: { key: ''; value: T; optional: false }
type Collapse<T extends Entry> = {
[E in Extract<T, { optional: false }> as E['key']]: E['value']
} & Partial<{ [E in Extract<T, { optional: true }> as E['key']]: E['value'] }> extends infer O
? { [K in keyof O]: O[K] }
: never
type FlattenObject<T> = Collapse<Explode<T>>
export function flatten<TObject extends Record<string, unknown>>(
object: TObject
): FlattenObject<TObject> {
const result: Record<string, unknown> = {}
function recurse(current: Record<string, unknown>, prefix = ''): void {
for (const key in current) {
const value = current[key]
const nextKey = prefix ? `${prefix}.${key}` : key
if (typeof value === 'object' && value !== null) {
recurse(value as Record<string, unknown>, nextKey)
} else {
result[nextKey] = value
}
}
}
recurse(object)
return result as FlattenObject<TObject>
}