0

I'm using this code with Svelte 3:

REPL: https://svelte.dev/repl/bf73fffc1b9442dfbcd492eaa9c048e1?version=3.35.0

<script lang="ts">
  const players = {
    men: {
      john: "high",
      bob: "low",
    },
  };

  // const player = "bob"
  $: player = "bob";

  const test = players.men[player];

  console.log(test); //prints undefined
</script>

{player} //bob
{test} //undefined

Typescript even tells me this:

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ john: string; bob: string; }'.
  No index signature with a parameter of type 'string' was found on type '{ john: string; bob: string; }'.ts(7053)

If I use const player = "bob" it works!

Why?

Fred Hors
  • 3,258
  • 3
  • 25
  • 71
  • 1
    Because a `const` can't change to something else at runtime. How is the compiler supposed to know that you won't change it if it's mutable? – Jared Smith Mar 23 '21 at 15:46
  • See also https://github.com/Microsoft/TypeScript/blob/1db4f96fd14fc3de5ae3704e925afd6474cfb8f5/doc/spec.md#4.13 – Jared Smith Mar 23 '21 at 15:48
  • This makes sense, but do you at least admit that the typescript error is misleading? How to solve it? – Fred Hors Mar 23 '21 at 15:48
  • 1
    FWIW I don't think it's misleading at all, it says pretty plainly that you can't access your object literal with a random string as that would be completely unsafe. I don't know enough about Svelte to explain how to make it play nice with TS here. Typically in TS you use statically knowable accessors like string literals or members of an enum or if it is really an unsafe access then you cast the accessor to `keyof whatever` and then it's on you to check the value because the compiler can't help you. But you seem to be using something svelty and I don't know enough to tell you. – Jared Smith Mar 23 '21 at 15:53
  • Thanks. Where can I use `keyof` here? – Fred Hors Mar 23 '21 at 15:55
  • 1
    `const test = players.men[player as keyof typeof players.men]` https://www.typescriptlang.org/play?#code/MYewdgzgLgBADgGwIYE8CmAnCMC8MDeAUDCTALZpgBcBxpJAViABbUwBEzAlgObPsAaOvQBGIETXYIQAd0HCYAXyElFAbkJ0A9FpihIsRKky4OYke0II0h5Ogyn259hsL7oMKGg94j9iAB0FGAA2n4mSNgA1mgoIABmnihwaAnwdpiBwQC6rppAA Again, just so we're clear, by doing that you are saying to the compiler "I got this", it won't help you if there is no player with that name, it'll be up to you to check. See also my self-answered question here https://stackoverflow.com/questions/66233214/why-can-typescript-not-figure-out-the-type-in-my-code – Jared Smith Mar 23 '21 at 15:58
  • Amazing! It works! You can answer and I will accept it! – Fred Hors Mar 23 '21 at 16:06
  • 1
    Done and done. Glad it helped! – Jared Smith Mar 23 '21 at 16:24

1 Answers1

2

You can't use a random string to access an object safely, which is why the compiler complains. The reason it doesn't complain when you use const is that a constant string literal is known at compile time, and it can't change at runtime, so the compiler knows it's safe. There are a few ways around this, but the easiest (N.B. not safest) way is a cast:

  const players = {
    men: {
      john: "high",
      bob: "low",
    },
  };

  // const player = "bob"
  let player = "bob";

  const test = players.men[player as keyof typeof players.men];

There may also exist a safer way to do this with the Svelte thing you are trying to use, typically when writing this sort of thing in TS I will use string literals or an enum to have the compiler ensure I'm not getting an unexpected undefined:

enum Males {
  JOHN = "john",
  BOB = "bob"
}

const players: {
  men: {
    [key in Males]: string;
  }
} = {
  men: {
    john: "high",
    bob: "low",
  },
};

const test2 = players.men[Males.JOHN];

Playground

Jared Smith
  • 19,721
  • 5
  • 45
  • 83