Convert TypeScript Map to Readonly Map generically

In order to better communicate immutability of an object during development, I want to convert an entire object to read only. Basically, the class has a copy() function, which should return a read-only copy of itself. The important part is only, that the type is read only, so compiler errors are thrown, during development. The actual runtime behavior is not as important.

Using the Readonly<> utility type, only primitive types are cast to read only. But this class also has a Map, which even is generic. How can I convert the Map to a ReadonlyMap without losing the generic values?

Here is a small code snippet to illustrate the problem:

class MyClass<T> {
    public name: string = "Name";
    public data: Map<string, T> = new Map<string, T>();

    // "this" is used for correct typing of inheriting classes here
    public copy(): ReadonlyClass<this> {
        return structuredClone(this) as ReadonlyClass<this>;
    }
}

export type ReadonlyClass<T extends MyClass<any>> = {
  [P in keyof T]: T[P] extends Map<string, any>
    ? ReadonlyMap<string, /* what can I put here? */>
    : Readonly<T[P]>;
};

I’ve tried getting the type via the values() function of the map: ReturnType<T[P]['values']>, but that returns an IterableIterator, and as far as I can see, there is no way to extract the value type from there.

Is this just not possible with TypeScripts current feature set?

Thanks!

>Solution :

The infer keyword comes in handy here. We can use it to infer both the key-type K and the value-type V of the Map and use them to construct the ReadonlyMap<K, V>.

export type ReadonlyClass<T extends MyClass<any>> = {
  [P in keyof T]: T[P] extends Map<infer K, infer V>
    ? ReadonlyMap<K, V>
    : Readonly<T[P]>;
}

Playground

Leave a Reply