1

Apologies if this has been asked already, I wasn't quite sure how to search for it.

Given a pre-defined type that maps keys to a type of value, is it possible to create a typed Map where the second parameter to the generic is inferred from the value used for the first?

For instance, I can do this with an object:

interface EntitiesMap {
    persons: Person
    pets: Pet
}

type EntitiesObject = { [K in keyof EntitiesMap]?: Array<EntitiesMap[k]> }

const a: EntitiesObject {
  persons: [person1, person2]
}

const b: EntitiesObject {
  persons: [person1, person2],
  pets: [pet1]
}

But I can't figure out how (or if it's possible) to do something similar with a Map:

// doesn't work
type EntitiesMap = Map<K in keyof EntitiesMap, Array<EntitiesMap[k]>>

Here's a more full example (and here it is in a TS playground)

interface Entity {
    type: string
}

interface Person extends Entity {
    phoneNumber: number
}

interface Pet extends Entity {
    favoriteFood: string
}

interface EntitiesMap {
    person: Person
    pet: Pet
}

class Household {
    inhabitantsObject: { [K in keyof EntitiesMap]?: EntitiesMap[K][] } = {}
    /* Can I do this with a Map() ? */
    inhabitantsMap: Map<keyof EntitiesMap, EntitiesMap[K]> = new Map()
    // inhabitantsMap: Map<K extends keyof EntitiesMap, EntitiesMap[K]> = new Map()
    // inhabitantsMap: Map<K in keyof EntitiesMap, EntitiesMap[K]> = new Map()

    getInhabitantsFromObj<T extends keyof EntitiesMap>(type: T) {
        return this.inhabitantsObject[type]
    }
    getInhabitantsFromMap<T extends keyof EntitiesMap>(type: T) {
        return this.inhabitantsMap.get(type)
    }
}

const house = new Household()

const peopleFromObj = house.getInhabitantsFromObj('person')
const petsFromObj = house.getInhabitantsFromObj('pet')

// these two values are 'any', but i'm guessing that is because
// inhabitantsMap does not have a valid type
const peopleFromMap = house.getInhabitantsFromMap('person')
const petsFromMap = house.getInhabitantsFromMap('pet')
Good Idea
  • 2,481
  • 3
  • 18
  • 25
  • `Map` doesn't work that way, it can't associate a specific key with a specific value, it simply takes a set of keys, and a set of values. – Patrick Roberts Nov 25 '21 at 03:18
  • The closest you can get is `Map`, but unlike the object-based interface, `house.getInhabitantsFromMap('person')` and `house.getInhabitantsFromMap('pet')` both return the types `Person | Pet | undefined` instead of `Person | undefined` and `Pet | undefined` respectively. – Patrick Roberts Nov 25 '21 at 03:25
  • In the [answer](https://stackoverflow.com/a/54908008/2887218) to the linked question I show one approach how to give typings to `Map` so you can use them to hold the key/value pairings of an arbitrary object type (and I call it `ObjectMap` to give it a new name and not try to overload the default `Map` typings). If I use that code here I get [this](https://tsplay.dev/m3aBAw), which behaves how you want. But also in that answer I wonder if it's worth it, since a plain object has this functionality and is a lot simpler to use. Anyway, hope that helps! – jcalz Nov 25 '21 at 03:30

0 Answers0