Es6 Map in Typescript

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:

  1. keys can only be of type string or number
  2. 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



Leave a reply



Submit