Search code examples
typescripttemplate-literals

Type for recursively remapping keys of "flattened" object into nested one (using template literals)


I'm attempting to express type to a function that takes an object with keys as dot delimited paths and returns a new object, where all sub-paths are expanded. I can't wrap my head around how would one preserve the original type for a given path while recursively 'de-structuring' them.

// Dummy structure
const flattenedObject = {
    'd.a': 1,
    'd.b': true,
};

type FlattenedObject = typeof flattenedObject;

// Expected type (or union of sub-paths: `{ d: { a: number } } | { d: { b: boolean } }`)
interface NestedObject {
    d: {
        a: number;
        b: boolean;
    }
}

// Attempt
type MapNestedKeys<R, N extends keyof R = keyof R, N0 = N> = N0 extends string
  ? N0 extends `${infer K1}.${infer K2}`
    ? { [K in N as `${K1}`]: MapNestedKeys<R, N, K2> }
    : { [K in N as N0]: R[K] }
  : {};

// Gives union, but types for nested keys are now union on all key types
type Test = MapNestedKeys<FlattenedObject>;

Link to TS Playground


Solution

  • An alternative approach that works a bit better with various depths of the path (e.g. 'd.a.c')

    type MapNestedKeys<T, P = keyof T> = 
       P extends `${infer K1}.${infer K2}` & keyof T ?
          MapNestedKeys<{[S in K1]: MapNestedKeys<{[S1 in K2]: T[P]}>}>  : 
       P extends string & keyof T ? {[S in P]: T[P]} : {}