2

I am building a card game and have the following types:

type Rank = "A"| "2"| "3"| "4"| "5"| "6"| "7"| "8"| "9"| "T"| "J"| "Q"| "K";

interface IPowerCards {
  [key: Rank]: any
}

I also have the following object which I am trying to access with a rank string:

const powerCards: IPowerCards = {
  "A": {
    canPlayOnAnyCard: true,
    canChangeSuit: true,
  },
  "2": {
    canCounterPenaltyCard: true,
    penaltyAmount: 2,
  },
  ...,
};

All the keys on this object are valid Rank types. I am trying to access values on the powerCards object using the following code:

const rank = getRank(card) as Rank;
return rank && powerCards[rank]?.canCounterPenaltyCard

However, I get the error Element implicitly has an 'any' type because expression of type 'Rank' can't be used to index type 'IPowerCards'. Property 'A' does not exist on type 'IPowerCards'. This error message doesn't make sense to me because I am ensuring that the rank variable, which is the variable I am using to access the powerCards object, is of Rank type, which is what I have specified in my IPowerCards type definition. Where am I going wrong? Thanks

dazzaondmic
  • 345
  • 4
  • 14
  • Your `IPowerCards` type is invalid. You should have a prominent error message there. If you don't, something is very weird with your setup. If you do, then that is very important information that you should include in your question. Which is it? – jcalz Apr 15 '22 at 20:55
  • @jcalz I do not have an error message so I assume "something is weird with my setup". Why is it invalid and how do I make it valid. This is my first time using Typescript so what's obvious to you might not be obvious to me. Thanks – dazzaondmic Apr 15 '22 at 20:59
  • Hmm, that's strange. Ideally example code should be a [mre] that will demonstrate your issue to others. If I put your code in a standalone IDE like [this](https://tsplay.dev/w65peW), I'm not seeing what you're seeing. In particular, the index signature `[key: Rank]: any` is invalid and you probably need a mapped type instead; see https://stackoverflow.com/questions/59329193/combining-generics-with-index-type . If you fix that, does your issue go away? Can you double check that you are somehow compiling `IPowerCards` with no error? There's no version of TS I know of that would accept it. – jcalz Apr 15 '22 at 21:06
  • @jcalz Yes, very strange indeed. I'm going to have to dig into my typescript config and do more research. For the meantime, the other answer solves my problem. Thanks for the help! – dazzaondmic Apr 15 '22 at 21:09
  • Does this answer your question? [Element implicitly has an 'any' type because expression of type 'string' can't be used to index](https://stackoverflow.com/questions/57086672/element-implicitly-has-an-any-type-because-expression-of-type-string-cant-b) – Vega Jul 11 '22 at 09:54

1 Answers1

2

Use a Record for IPowerCards instead:

type IPowerCards = Record<Rank, any>

If you want to have the properties optional:

type IPowerCards = {
  [key in Rank]?: any
}
Tobias S.
  • 21,159
  • 4
  • 27
  • 45
  • Thanks. Is there a way to achieve this using interfaces as opposed to types? – dazzaondmic Apr 15 '22 at 20:59
  • I think not. You need to use a Mapped Type to use a Union as keys in an object. – Tobias S. Apr 15 '22 at 21:01
  • Thanks. One other issue: It gives me the error `...is missing the following properties from type 'IPowerCards': 3, 4, 5, 6, and 3 more` I only want to have a subset of the ranks in the `powerCards` object. How do I specify that in the type? – dazzaondmic Apr 15 '22 at 21:03
  • 1
    You can write `interface IPowerCards extends Record> {}` or `extends Partial>` or whatever, but I wouldn't do this unless I had some articulated reason why an interface was preferable to a mapped type. – jcalz Apr 15 '22 at 21:08