I would like to store a mapping of string -> string in a Typescript object, and enforce that all of the keys map to strings. For example:
var stuff = {};
stuff["a"] = "foo"; // okay
stuff["b"] = "bar"; // okay
stuff["c"] = false; // ERROR! bool != string
Is there a way for me to enforce that the values must be strings (or whatever type..)?
A quick update: since Typescript 2.1 there is a built in type
Record<T, K>
that acts like a dictionary.An example from docs:
TypeScript 2.1 Documentation on
Record<T, K>
The only disadvantage I see to using this over
{[key: T]:K
is that you can encode useful info on what sort of key you are using in place of "key" e.g. if your object only had prime keys you could hint at that like so:{[prime: number]: yourType}
.Here's a regex I wrote to help with these conversions. This will only convert cases where the label is "key". To convert other labels simply change the first capturing group:
Find:
\{\s*\[(key)\s*(+\s*:\s*(\w+)\s*\]\s*:\s*([^\}]+?)\s*;?\s*\}
Replace:
Record<$2, $3>
Define interface
Enforce key to be a specific key of Settings interface
Here, the interface
AgeMap
enforces keys as strings, and values as numbers. The keywordname
can be any identifier and should be used to suggest the syntax of your interface/type.You can use a similar syntax to enforce that an object has a key for every entry in a union type:
You can, of course, make this a generic type as well!
10.10.2018 update: Check out @dracstaxi's answer below - there's now a built-in type
Record
which does most of this for you.1.2.2020 update: I've entirely removed the pre-made mapping interfaces from my answer. @dracstaxi's answer makes them totally irrelevant. If you'd still like to use them, check the edit history.
Building on @shabunc's answer, this would allow enforcing either the key or the value — or both — to be anything you want to enforce.
Should also work using
enum
instead of atype
definition.@Ryan Cavanaugh's answer is totally ok and still valid. Still it worth to add that as of Fall'16 when we can claim that ES6 is supported by the majority of platforms it almost always better to stick to Map whenever you need associate some data with some key.
When we write
let a: { [s: string]: string; }
we need to remember that after typescript compiled there's not such thing like type data, it's only used for compiling. And { [s: string]: string; } will compile to just {}.That said, even if you'll write something like:
This just won't compile (even for
target es6
, you'll geterror TS1023: An index signature parameter type must be 'string' or 'number'.
So practically you are limited with string or number as potential key so there's not that much of a sense of enforcing type check here, especially keeping in mind that when js tries to access key by number it converts it to string.
So it is quite safe to assume that best practice is to use Map even if keys are string, so I'd stick with: