1

I have a object map of T with value that extends an interface / class or in a similar example members in a union. I would like to lookup the value of a key, K in T and determine the specific subtype or union member, i.e., I don't want the derived type to evaluate to the union or interface. Here an example of an object map with values that extend an interface

class Animal { move() { } }

class Cat extends Animal { meow() { } }

class Dog extends Animal { bark() { } }


const animalLookup: { [K in 'cat' | 'dog']: typeof Animal } = {
  cat: Cat,
  dog: Dog,
}

Now when I lookup a key in animalLookup:

const result = animalLookup['cat'];
// => const result: typeof Animal;

This is expected.

What I've tried is making animalLookup a const with as const. This works as expect, but I loose the ability to make the index type of animalLookup a mapped type, so I can enforce the keys.

Is there a way to make this work so that I can achieve the following while annotating animalLookup with an index signature so I can constrain the keys

const result = animalLookup['cat'];
// => const result: typeof Cat;

EDIT: Added clarification to the question EDIT: updated example to correct values to constructor vs instance types

  • 1
    This is weird, you're putting *constructors* into the lookup object and hoping that the type will be *instance* types? Your example only doesn't error because your classes are empty. Perhaps you meant `cat: new Cat(), dog: new Dog()` instead? If not, what's the goal here? I think the issue here has to do with the lack of a `satisfies` operator in TypeScript that checks a value against a type instead of widening it, and if so, then the solution could be to implement `satisfies` yourself as a function like [this](//tsplay.dev/mq53Ym). Can you fix/clarify your example and get back to me? – jcalz Apr 11 '22 at 14:45
  • Hey @jcalz thanks for your response, I was hoping you might see this, I specifically filtered your answers on SO to see if you might have answered something similar. Thanks for all your help to this community, much appreciated. I'll update the question, but yes this is a map of constructor classes, and I'm hoping to get the the specific constructor as the result of the lookup when looking up an entry by its key – reflective_crystal_ball Apr 11 '22 at 17:24
  • You are still mixing up constructor types with instance types. You don't want `{[K in 'cat' | 'dog']: Animal}`; you want `{[K in 'cat' | 'dog': new () => Animal}`. As I said, the only reason why your example code isn't showing compiler errors is because your [classes are empty](https://github.com/Microsoft/TypeScript/wiki/FAQ#why-do-these-empty-classes-behave-strangely). You need to fix the code to demonstrate what you're trying to demonstrate before it is git to be answered. See the `BetterExampleCode` section [here](https://tsplay.dev/wOPAdN) and consider changing your example to that. – jcalz Apr 11 '22 at 17:40
  • Using and modifying your example, I think this is what I'm going for [here](https://www.typescriptlang.org/play?#code/MYGwhgzhAECCB2BLAtmE0De1kHsBuApgBQCUm0AvpQFDWiQwDCYALtAQB4sHwAmMCFGnLICOAO6lyVCrXpRoAERwBzdlx784SVOiwAjMACcA1lKwzadHPAhsIrRBABmiAjAC80ADwAVADQAfFIegT4AqurcfDC+wQCuAFzQ4WSh0PEA3HI2dtAOLE6u7oK6ADI4OCbxAA7QXgVFbhDeWADaANLQiPDQAOTArH3QAD79vKp9ALrJLACeNWLO2kLoFMEk2da2bGA6aBVVtfX5ji7NpQeV1TVEGNTQ0IMsycws-g-QEyrJyirUFE2OR20CM7niIDYXj2q0ONzaAyGU2yAHoUfUwsBcmwwRAIS9oG9skA) – reflective_crystal_ball Apr 11 '22 at 17:53
  • Okay, so could you [edit] your example code to use `typeof Animal` instead of `Animal` (and maybe add those methods so that the compiler doesn't think that, for example, `typeof Cat` and `typeof Dog` are [equivalent types](https://tsplay.dev/WKqODN))? – jcalz Apr 11 '22 at 17:56
  • Sounds good, updating, sorry for the confusion – reflective_crystal_ball Apr 11 '22 at 18:05
  • Great! And does the [suggested solution with `satisfiesXXX`](https://tsplay.dev/wjZ1vw) meet your needs or am I missing something? – jcalz Apr 11 '22 at 18:26
  • 1
    Yes it does! Thanks for the solution and for your general community help – reflective_crystal_ball Apr 11 '22 at 18:39
  • The basic issue is the lack of a `satisfies` operator as originally requested in [ms/TS#7481](https://github.com/microsoft/TypeScript/issues/7481) and now at [ms/TS#47920](https://github.com/microsoft/TypeScript/issues/47920). I've closed this as a duplicate of some others rather than write out yet another description of `satisfies` and how it works. Anyway, glad you found what you needed and good luck! – jcalz Apr 11 '22 at 18:46

0 Answers0