Skip to content

Instantly share code, notes, and snippets.

@YBogomolov
Last active July 11, 2023 09:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save YBogomolov/888c659fe1d59e5b1a1b8c0cdc3e73bb to your computer and use it in GitHub Desktop.
Save YBogomolov/888c659fe1d59e5b1a1b8c0cdc3e73bb to your computer and use it in GitHub Desktop.
Inflating objects
type Split<S extends string, D extends string> = S extends `${infer T}${D}${infer U}` ? [T, ...Split<U, D>] : [S];
type Join<T extends string[], D extends string> = T extends []
? ''
: T extends [infer F extends string, ...infer R extends string[]]
? `${F}${D}${Join<R, D>}`
: never;
type Inflate<T> = Compact<UnionToIntersection<{ [K in keyof T]: InflateInner<T[K], K & string>; }[keyof T]>>;
type UnionToIntersection<U> = (
U extends unknown ? (arg: U) => void : never
) extends (arg: infer I) => void ? I : never;
type Compact<T> = { [K in keyof T]: T[K] }
type InflateInner<X, K extends string> = Split<K, '.'> extends [
infer Head extends string,
...infer Rest extends string[]
]
? Head extends `${infer _}${infer _}`
? { [K in Head]: InflateInner<X, Join<Rest, '.'>> }
: X
: X;
const inflate = <T extends Record<string, unknown>>(input: T): Inflate<T> =>
Object.keys(input).reduce((obj, key) => {
const parts = key.split('.');
const reversedParts = parts.reverse();
// `parts` is a non-empty array, so having a type assertion here is safe:
const lastPart = reversedParts.shift() as string;
return Object.assign(
obj,
reversedParts.reduce((acc, part) => ({ [part]: acc }), { [lastPart]: input[key] })
);
}, {}) as Inflate<T>;
const inflated = inflate({ 'foo.bar.baz': 42, qux: 'hello', 'blem.blum': false });
console.log(inflated);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment