Home > Software design >  TypeScript Does not Properly Infer the Type of an Object's Property
TypeScript Does not Properly Infer the Type of an Object's Property

Time:05-21

I'm trying to create an object where each property is mapped to a class instance. The class type relies on two generics. When I access a property in my object, TypeScript infers the type of the property as the union type used to define the object rather than the type of the property itself.

How can I get TypeScript to infer the type of the property properly?

Here is a code snippet to help clarify the problem:

export class CacheWithExpiry<T extends CacheTypes, V extends CacheNames> {
    constructor() {
    }
    ...
}

export type CacheNames =
  'foo_1' |
  'foo_2'

type CacheTypes = string | number

type CacheMap<T extends CacheTypes, V extends CacheNames> = { [key: string]: CacheWithExpiry<T, V> }

export const cacheMap: CacheMap<CacheTypes, CacheNames> = {
  foo1Cache: new CacheWithExpiry<string, 'foo_1'>(), // Type is inferred as expected 'CacheWithExpiry<string, "foo_1">'
  foo2Cache: new CacheWithExpiry<number, 'foo_2'>()
} as const

cacheMap.foo1Cache// Type is inferred as 'CacheWithExpiry<CacheTypes, CacheNames>' instead of 'CacheWithExpiry<string, "foo_1">'

Edit 1: I also need to maintain type safety for cacheMap so removing the type completely from cacheMap wouldn't work. Example:

export const cacheMap = {
  foo1Cache: new CacheWithExpiry<string, 'foo_1'>(),
  foo2Cache: new CacheWithExpiry<number, 'foo_2'>(),
  foo3: 'I am not a cache class instance', // I still want this to throw an error
} as const

CodePudding user response:

You assigned the type CacheMap<CacheTypes, CacheNames> to the variable cacheMap. This type does not hold any information about the keys foo1Cache or foo12ache. All it knows is that there could be any key of type string which would hold a value of type CacheWithExpiry<CacheTypes, CacheNames>. That is why when you access a property of cacheMap, TypeScript will not know about the specific properties.

If you take away the type though, TypeScript will know the properties and their types:

export const cacheMap = {
  foo1Cache: new CacheWithExpiry<string, 'foo_1'>(),
  foo2Cache: new CacheWithExpiry<number, 'foo_2'>()
} as const

cacheMap.foo1Cache // foo1Cache: CacheWithExpiry<string, "foo_1">

The type of cacheMap is now:

const cacheMap: {
    readonly foo1Cache: CacheWithExpiry<string, "foo_1">;
    readonly foo2Cache: CacheWithExpiry<number, "foo_2">;
}

Playground


If you also want type validation when creating the cacheMap object, you need to pass the object literal to a generic function.

function createCacheMap<T extends CacheMap<CacheTypes, CacheNames>>(cacheMap: T): T {
  return cacheMap
}

export const cacheMap = createCacheMap({
  foo1Cache: new CacheWithExpiry<string, 'foo_1'>(),
  foo2Cache: new CacheWithExpiry<number, 'foo_2'>(),
  anything: "string" // Error: Type 'string' is not assignable to type 'CacheWithExpiry<CacheTypes, CacheNames>
})

cacheMap.foo1Cache // foo1Cache: CacheWithExpiry<string, "foo_1">

Playground

  • Related