Created
October 29, 2025 16:09
-
-
Save Nipsuli/b18f462f91db828a95abeba7738865db to your computer and use it in GitHub Desktop.
Flatten deep object with strongly typed keys
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Helpers to be used instead of Object | object | Any | |
| export type Primitive = string | number | boolean | symbol | bigint; | |
| export type KeyedObj = { [key: string]: Obj }; | |
| export type Obj = Primitive | Date | null | undefined | Obj[] | KeyedObj; | |
| export type FlattenObjectArrayKeys< | |
| T extends Record<string, unknown> | Primitive[] | Record<string, unknown>[], | |
| Key = T extends Record<string, unknown> ? keyof T : `[${number}]`, | |
| > = Key extends string | |
| ? T extends Record<string, unknown> | |
| ? T[Key] extends | |
| | Record<string, unknown> | |
| | Primitive[] | |
| | Record<string, unknown>[] | |
| ? `${Key}.${FlattenObjectArrayKeys<T[Key]>}` | |
| : `${Key}` | |
| : T extends Primitive[] | |
| ? `${Key}` | |
| : T extends Record<string, unknown>[] | |
| ? `${Key}.${FlattenObjectArrayKeys<T[number]>}` | |
| : never | |
| : never; | |
| export const isPrimitive = (v: unknown): v is Primitive => | |
| typeof v === "string" || | |
| typeof v === "number" || | |
| typeof v === "boolean" || | |
| typeof v === "bigint" || | |
| typeof v === "symbol"; | |
| export const isDate = (value: unknown): value is Date => { | |
| return value instanceof Date && !isNaN(value.getTime()); | |
| }; | |
| export const flatten = < | |
| T extends | |
| | Record<string | number | symbol, Obj> | |
| | Record<string | number | symbol, Obj>[], | |
| >( | |
| obj: T, | |
| ) => { | |
| const res = {} as Record<string, Primitive | Date | null | undefined>; | |
| const r = (acc: string, o: Obj): void => { | |
| if (isPrimitive(o) || isDate(o)) { | |
| res[acc] = o; | |
| } else if (Array.isArray(o)) { | |
| for (const [i, e] of o.entries()) { | |
| r(`${acc}${acc ? "." : ""}[${i.toString()}]`, e); | |
| } | |
| } else if (o !== null && o !== undefined) { | |
| for (const [k, v] of Object.entries(o)) { | |
| r(`${acc}${acc ? "." : ""}${k}`, v); | |
| } | |
| } else { | |
| res[acc] = o; | |
| } | |
| }; | |
| r("", obj); | |
| // Need to figure out if one could fix the internal types to | |
| // match things from the getgo | |
| return res as unknown as Record< | |
| FlattenObjectArrayKeys<T>, | |
| Primitive | Date | null | undefined | |
| >; | |
| }; | |
| /* | |
| Example | |
| */ | |
| const tstObj = { | |
| a: 1, | |
| b: [2, 3], | |
| c: { | |
| d: 4, | |
| e: [{ | |
| f: 5 | |
| }] | |
| } | |
| } | |
| const flattenedTstObj = flatten(tstObj) | |
| // Inferred type: | |
| // Record<"a" | `b.[${number}]` | "c.d" | `c.e.[${number}].f`, Primitive | Date | null | undefined> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment