Home > Net >  How to create a nested generic type with automatic infer
How to create a nested generic type with automatic infer

Time:07-13

Let say I want to create a "feature-config", which has the following type:

type FeatureConfig = {
   isActivated: boolean;
   childs?: {
      [key: string]: FeatureConfig;
   }
}

const config : FeatureConfig = {
   isActivated: true,
   childs: {
      bar: {
         isActivated: true,
         childs: {
            foo: {
              isActivated: true
            }
         }
      },
      foo: {
         isActivated: false,
         childs: {
            bar: {
              isActivated: true
            }
         }
      }
   }
}

All fine and typed-checked this far. Now I want to create a "build"-function which takes a Config and returns a "feature-tree" which I can check if a certain feature is activated. My goal with the function is to have the following syntax/functionality:

/// How should the types of this function look like??
const createFeatureChecker = (config: FeatureConfig) => {
   //...implementation
}

const featureChecker = createFeatureChecker(config);

featureChecker.bar.foo.isActivated // true
featureChecker.foo.isActivated // false
featureChecker.foo.bar.isActivated // false (since featureChecker.foo.isActivated is false)

I have no problems implementing the function logic in plain old javascript, but I'm struggling to type createFeatureChecker in a way so that I can use the featureChecker in a type-safe way.

Any ideas on how to solve my issue?

CodePudding user response:

You will need to use as const declaring config or pass it as an object literal.

const config = {
   isActivated: true,
   childs: {
      bar: {
         isActivated: true,
         childs: {
            foo: {
              isActivated: true
            }
         }
      },
      foo: {
         isActivated: false,
         childs: {
            bar: {
              isActivated: true
            }
         }
      }
   }
} as const

A typed createFeatureFunction would look like this:

type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never

type CreateFeatureChecker<T extends FeatureConfig, Activated extends boolean = true> = {
    isActivated: Activated extends true ? T["isActivated"] : false
} & {
    -readonly [K in keyof (T["childs"])]: 
      (T["isActivated"] extends false ? false : Activated extends false ? false : true) extends infer A extends boolean
        ? T["childs"][K] extends { isActivated: infer B extends boolean } 
            ? T["childs"][K] extends { childs: infer C extends {
                [key: string]: FeatureConfig;
              }} 
                ? Expand<CreateFeatureChecker<{ isActivated: B, childs: C }, A>>
                : Expand<CreateFeatureChecker<{ isActivated: B }, A>>
            : never
        : never
}

const createFeatureChecker = <T extends FeatureConfig>(config: T): Expand<CreateFeatureChecker<T>> => {
   return {} as any
}

Some tests so see if it's working:

const featureChecker = createFeatureChecker(config);

const a = featureChecker.bar.foo.isActivated // true
const b = featureChecker.foo.isActivated // false
const c = featureChecker.foo.bar.isActivated // false

Playground

  • Related