ES6 Map in Typescript
EDIT (Jun 5 2019): While the idea that "TypeScript supports Map
natively" is still true, since version 2.1 TypeScript supports something called Record
.
type MyMapLikeType = Record<string, IPerson>;
const peopleA: MyMapLikeType = {
"a": { name: "joe" },
"b": { name: "bart" },
};
Unfortunately the first generic parameter (key type) is still not fully respected: even with a string
type, something like peopleA[0]
(a number
) is still valid.
EDIT (Apr 25 2016): The answer below is old and should not be considered the best answer. TypeScript does support Maps "natively" now, so it simply allows ES6 Maps to be used when the output is ES6. For ES5, it does not provide polyfills; you need to embed them yourself.
For more information, refer to mohamed hegazy's answer below for a more modern answer, or even this reddit comment for a short version.
As of 1.5.0 beta, TypeScript does not yet support Maps. It is not yet part of the roadmap, either.
The current best solution is an object with typed key and value (sometimes called a hashmap). For an object with keys of type string
, and values of type number
:
var arr : { [key:string]:number; } = {};
Some caveats, however:
- keys can only be of type
string
ornumber
- It actually doesn't matter what you use as the key type, since numbers/strings are still accepted interchangeably (only the value is enforced).
With the above example:
// OK:
arr["name"] = 1; // String key is fine
arr[0] = 0; // Number key is fine too
// Not OK:
arr[{ a: "a" }] = 2; // Invalid key
arr[3] = "name"; // Invalid value
Creating a map in typescript
You may be confused with Map
and JavaScript objects
. A simple javascript object such as {}
is not the same thing as new Map()
. You've defined the typeMap
as a "map", and then you are trying to update it with an object. Either use Map
and then use .get/.set
or use an object
with your the interface you want:
// Use type
const typeMap = new Map<string, string>();
typeMap.set('key1', 'value1');
// would print value1
console.log(typeMap.get('key1'));
// Or use an interface
interface IMap {
[key: string]: string;
}
const objectMap: IMap = {};
objectMap.key1 = 'value1';
const key = 'key1';
// in both cases, prints value1
console.log(objectMap.key1);
console.log(objectMap(key));
Set a ES6 Map from an array of objects in Typescript?
Is there a simpler way or a method I am missing on the Map type?
"Simple" is a bit subjective. I usually use this approach because it fits in one line and because initializing a Map
with some elements might be faster than initializing it and then adding elements.
const myObjects: { id: number; data: string; }[] = [{id: 1, data: "blah"}, {id: 2, data: "foo"}, {id: 3, data: "bar"}];
const myMap = new Map(myObjects.map(obj => [obj.id, obj]));
Explanation: The Map
constructor takes an itrerable of iterables (in this case an array of arrays) as the first parameter. Each element in the main array is an entry in the map, with the key being the element's first element and the value being the element's second element.
TypeScript - How to use array map to an ES 6 Map?
You need to do type assertion
and tell typescript that your mappedData
is of type Array<[string,string]>
instead of string[][]
which is a sub type for Array<[any,any]>
as needed by Map
constructor.
Do
const mappedData = data.map(x => [x.key, x.value] as [string, string]);
instead of
const mappedData = data.map(x => [x.key, x.value]);
and also
drop the values()
call as pointed out in comments.
Object is possibly 'undefined' using ES6 Map.get() right after Map.set()
This is somewhat of a design limitation of TypeScript; the compiler can't keep track of the state of the Map
that way. There's a (fairly old) feature request at microsoft/TypeScript#9619 to support control flow analysis for Map
methods.
It can't be done without changing the language, though. Currently Map<K, V>
is declared as an interface
. The set()
method has a return type of this
(because calling set()
returns the same Map
instance). And the get()
method has a return type of V | undefined
.
But there's not really a way to say that set()
mutates the state of the instance so that get()
with the "right" keys will return just V
instead of V | undefined
. In some sense you want calling myMap.set("test", 1)
to change the type of myMap
from Map<string, number>
(which doesn't know what keys have actual values) to something like Map<string, number> & {get("test"): number}
. Presumably you'd want myMap.delete("test")
to change the type to Map<string, number> & {get("test"): undefined}
. But TypeScript doesn't let you represent arbitrary type mutations like this. There are assertion methods but there are lots of caveats, the biggest of which is that they only strictly narrow a type, and if the compiler doesn't see the behavior as narrowing it won't work.
So right now it's basically a limitation. That doesn't mean that myMap.get('test')
can actually be undefined
, just that the compiler doesn't know this.
Rather than wait for some possible future version of TypeScript to support this use case, you might want to work around it or refactor. The easiest workaround is to just accept that you're cleverer than the compiler and use a non-null assertion to tell it that a value cannot be null
or undefined
:
const myMap = new Map<string, number>();
myMap.set("test", 1);
myMap.get("test")! / 2; // <-- no error
If you really need the compiler to keep track of such things, then you can refactor to use method chaining; instead of having a single object named myMap
whose state is different every time you call set()
, you use multiple objects, each of which has a single constant state... when you call set()
you get a new object. It could look like this:
const init = new MyMap();
//const init: MyMap<{}>
const afterSet = init.set("test", 1);
//const afterSet: MyMap<Record<"test", number>>
const val = afterSet.get("test") / 2;
Or like this:
const m = new MyMap().set("test", 1).set("foo", "abc").set("baz", false);
m.get("foo").toUpperCase();
m.get("test").toFixed();
m.get("blah") // error! not a valid key
Here's one possible implementation (only dealing with get()
and set()
but you could implement other methods too):
interface MyMap<T extends object = {}> {
set<K extends keyof T>(k: K, v: T[K]): this;
set<K extends string, V>(k: Exclude<K, keyof T>, v: V): MyMap<T & Record<K, V>>;
get<K extends string>(k: K): K extends keyof T ? T[K] : undefined;
}
const MyMap = Map as new () => MyMap;
Playground link to code
Typescript : Es6 Map TS Type Relation
Define a custom type that overrides Map
to direct the call signatures to what you want.
interface CustomMap<T, U> extends Map<T | U, T | U> {
get(key: T): U | undefined;
get(key: U): T | undefined;
set(key: T, value: U): this;
set(key: U, value: T): this;
}
const map: CustomMap<string, number> = new Map();
map.set(1, 'a'); // OK
map.set('a', 1); // OK
map.set(1, 1); // error as expected
map.set('a', 'a'); // error as expected
map.size; // OK, not overridden, still a number
map.delete(1); // OK
map.delete('a'); // OK
Map initialization in typescript
It is initialized correctly, but printing it does not print all values like an array. But you can check it by just accessing the keys - they exist:
let map = new Map([
[
"api/service/method",
{
uriPath: {}
}
],
[
"\"type\": \"html\"",
{
body: {}
}
]
]);
console.log(map);
console.log(map.get('api/service/method'));
console.log(map.get('"type": "html"'));
.map() a Javascript ES6 Map?
So .map
itself only offers one value you care about...
That said, there are a few ways of tackling this:
// instantiation
const myMap = new Map([
[ "A", 1 ],
[ "B", 2 ]
]);
// what's built into Map for you
myMap.forEach( (val, key) => console.log(key, val) ); // "A 1", "B 2"
// what Array can do for you
Array.from( myMap ).map(([key, value]) => ({ key, value })); // [{key:"A", value: 1}, ... ]
// less awesome iteration
let entries = myMap.entries( );
for (let entry of entries) {
console.log(entry);
}
Note, I'm using a lot of new stuff in that second example...
...Array.from
takes any iterable (any time you'd use [].slice.call( )
, plus Sets and Maps) and turns it into an array... ...Maps, when coerced into an array, turn into an array of arrays, where el[0] === key && el[1] === value;
(basically, in the same format that I prefilled my example Map with, above).
I'm using destructuring of the array in the argument position of the lambda, to assign those array spots to values, before returning an object for each el.
If you're using Babel, in production, you're going to need to use Babel's browser polyfill (which includes "core-js" and Facebook's "regenerator").
I'm quite certain it contains Array.from
.
How to define Typescript Map of key value pair. where key is a number and value is an array of objects
First thing, define a type or interface for your object, it will make things much more readable:
type Product = { productId: number; price: number; discount: number };
You used a tuple of size one instead of array, it should look like this:
let myarray: Product[];
let priceListMap : Map<number, Product[]> = new Map<number, Product[]>();
So now this works fine:
myarray.push({productId : 1 , price : 100 , discount : 10});
myarray.push({productId : 2 , price : 200 , discount : 20});
myarray.push({productId : 3 , price : 300 , discount : 30});
priceListMap.set(1 , this.myarray);
myarray = null;
(code in playground)
Related Topics
Batchsize Field Name Ignored in Field Projection
How to Send a Message to a Particular Client with Socket.Io
Share Variables Between Files in Node.Js
Run JavaScript in Visual Studio Code
Get Query String Parameters Url Values with Jquery/JavaScript (Querystring)
Switch Case Not Showing Correct Results
Why Do Arrow Functions Not Have the Arguments Array
How to Convert a String into a Math Operator in JavaScript
The Preferred Way of Creating a New Element with Jquery
Reactjs: Prevent Multiple Times Button Press
Google Bar Chart Cannot Change Individual Bar Color
Advanced JavaScript: Why Is This Function Wrapped in Parentheses
How to Verify Jquery Ajax Events with Jasmine
Date Parsing in JavaScript Is Different Between Safari and Chrome
Google Maps Move Marker with Lat/Lng from Ajax Success Returned Data
Adding Prototype to JavaScript Object Literal
How to Reverse an Array in JavaScript Without Using Libraries