Typing a Typescript Object Indexed Item Type?

I would like to keep the string mapping → string in the Typescript object and ensure that all keys are displayed in strings. For example:

var stuff = {}; stuff["a"] = "foo"; // okay stuff["b"] = "bar"; // okay stuff["c"] = false; // ERROR! bool != string 

Is there a way to get me to have the values ​​be strings (or any type ..)?

+199
typescript
Nov 09
source share
6 answers
 var stuff: { [s: string]: string; } = {}; stuff['a'] = ''; // ok stuff['a'] = 4; // error // ... or, if you're using this a lot and don't want to type so much ... interface StringMap { [s: string]: string; } var stuff2: StringMap = { }; // same as above 
+361
Nov 09 '12 at 20:08
source share

I carry this file with me wherever I go

 export interface StringTMap<T> { [key: string]: T; }; export interface NumberTMap<T> { [key: number]: T; }; export interface StringAnyMap extends StringTMap<any> {}; export interface NumberAnyMap extends NumberTMap<any> {}; export interface StringStringMap extends StringTMap<string> {}; export interface NumberStringMap extends NumberTMap<string> {}; export interface StringNumberMap extends StringTMap<number> {}; export interface NumberNumberMap extends NumberTMap<number> {}; export interface StringBooleanMap extends StringTMap<boolean> {}; export interface NumberBooleanMap extends NumberTMap<boolean> {}; 

StringTMap and NumberTMap are generic and can be used to create maps of any type (via let myTypeMap: StringTMap<MyType> = {} ). The rest are useful predefined mappings between generic literal types.

The important syntax here is { [key: string]: T; } { [key: string]: T; } , which indicates that the interface uses an object literal with keys of type string (the word key can be any identifier and should be used to indicate the importance of the key) and enter T values. If you want to create an object with string "names" for keys and boolean values ​​(and not use the inheritance described above), its interface will be { [name: string]: boolean } .

You can use the same syntax to ensure that the object has a key for each record in the join type:

 type DayOfTheWeek = "sunday" | "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday"; type ChoresMap = { [day in DayOfTheWeek]: string }; const chores: ChoresMap = { // ERROR! Property 'saturday' is missing in type '...' "sunday": "do the dishes", "monday": "walk the dog", "tuesday": "water the plants", "wednesday": "take out the trash", "thursday": "clean your room", "friday": "mow the lawn", }; 

Of course, you can also make this a universal type!

 type DayOfTheWeek = "sunday" | "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday"; type DayOfTheWeekMap<T> = { [day in DayOfTheWeek]: T }; const chores: DayOfTheWeekMap<string> = { "sunday": "do the dishes", "monday": "walk the dog", "tuesday": "water the plants", "wednesday": "take out the trash", "thursday": "clean your room", "friday": "mow the lawn", "saturday": "relax", }; const workDays: DayOfTheWeekMap<boolean> = { "sunday": false, "monday": true, "tuesday": true, "wednesday": true, "thursday": true, "friday": true, "saturday": false, } 

10/10/2018 update: See @dracstaxi's answer below - now there is a built-in Record type that does this for you.

+83
01 Sep '16 at 21:35
source share

Quick update: since Typescript 2.1 there is a built-in type Record<T, K> that acts like a dictionary.

Example from the documentation:

 // For every properties K of type T, transform it to U function mapObject<K extends string, T, U>(obj: Record<K, T>, f: (x: T) => U): Record<K, U> const names = { foo: "hello", bar: "world", baz: "bye" }; const lengths = mapObject(names, s => s.length); // { foo: number, bar: number, baz: number } 

TypeScript 2.1 documentation for Record<T, K>

The only drawback that I see when using over {[key: T]:K is that you can encode useful information about what type of key you use instead of the "key", for example, if your object had only simple keys that you might hint at is: {[prime: number]: yourType} .

Here is a regex that I wrote to help with these conversions. This converts only those cases where the label is the "key". To convert other labels, just change the first capture group:

Find: \{\s*\[(key)\s*(+\s*:\s*(\w+)\s*\]\s*:\s*([^\}]+?)\s*;?\s*\}

Replace: Record<$2, $3>

+50
Jul 03 '18 at 19:31
source share

@ Ryan Cavanaugh's answer is completely in order and still valid. Nevertheless, it is worth adding that with Fall'16, when we can say that ES6 is supported by most platforms, it is almost always better to stick to the Map when you need to associate some data with some key.

When we write let a: { [s: string]: string; } let a: { [s: string]: string; } , we need to remember that after typescript compiled there is no such thing as type data, it is used only for compilation. And {[s: string]: string; } only {} will be compiled.

However, even if you write something like:

 class TrickyKey {} let dict: {[key:TrickyKey]: string} = {} 

It just won't compile (even for target es6 you get error TS1023: An index signature parameter type must be 'string' or 'number'.

Thus, in practice, you are limited to a string or number as a potential key, so there is not much of a feeling of force type checking here, especially considering that when js tries to access the key by number, it converts it to a string.

Thus, it is completely safe to assume that it is best practice to use a map, even if the keys are strings, so I would stick with:

 let staff: Map<string, string> = new Map(); 
+7
Sep 30 '16 at 23:17
source share

Based on @shabunc's answer, this will force the use of either a key or a value - or both, whatever you require.

 type IdentifierKeys = 'my.valid.key.1' | 'my.valid.key.2'; type IdentifierValues = 'my.valid.value.1' | 'my.valid.value.2'; let stuff = new Map<IdentifierKeys, IdentifierValues>(); 

You should also use enum instead of defining type .

+1
Nov 19 '17 at 11:13
source share

Define Interface

 interface Settings { lang: 'en' | 'da'; welcome: boolean; } 

Use the key to be the specific key of the settings interface

 private setSettings(key: keyof Settings, value: any) { // Update settings key } 
+1
Dec 11 '18 at 5:36
source share



All Articles