Home > Software engineering >  Given an object of classes, how to type the object of initialized class instances in Typescript?
Given an object of classes, how to type the object of initialized class instances in Typescript?

Time:12-15

I have an object that looks like this:

const classMap = {service1: Service1, service2: Service2}

Where Service1 and Service2 are reference to classes, there can be a few hundreds classes.

Now I have an object that is dynamically created, which corresponds to this:

const instanceMap = {service1: new classMap.service1(), service2: new classMap.service2()}

And I want to type this instanceMap (type deduction failed here because the object is dynamically created).

The naive approach of "typeof classMap" doesn't work. When I use instanceMap.service1, I don't see the methods of Service1 classes in auto-complete. It seems the typeof operator is doing a typeof ServiceX, which erases all the methods of ServiceX class.

CodePudding user response:

You can make a mapped type over the properties of classMap, where you apply the InstanceType<T> utility type to each property value:

const classMap = { service1: Service1, service2: Service2 };
type ClassMap = typeof classMap;
type InstanceMap = { [K in keyof ClassMap]: InstanceType<ClassMap[K]> };

This is equivalent to

/* type InstanceMap = {
    service1: Service1;
    service2: Service2;
} */

And therefore you can annotate instanceMap with that type without error:

const instanceMap: InstanceMap = {
    service1: new classMap.service1(),
    service2: new classMap.service2()
}; // okay

Now, if you want to make instanceMap dynamically, then you will also need mapped types in your function, and make liberal use of type assertions inside the implementation, as the compiler isn't smart enough to follow the logic. For example:

function makeInstanceMap<T extends Record<keyof T, object>>(
    classMap: { [K in keyof T]: new () => T[K] }): T {
    return Object.fromEntries(
        Object.entries(classMap).map(([k, v]) => [k, new (v as any)()])
    ) as T;
}

const instanceMap = makeInstanceMap(classMap);
/* const instanceMap: {
    service1: Service1;
    service2: Service2;
} */

Playground link to code

  • Related