In my application, I need a map of objects per date. As typescript has both a Map
and a Date
objects, I expected this to be as easy as.
let map: Map<Date, MyObject> = new Map<Date, MyObject>();
And use set
to add new keys and values pairs. But then I've realized that I cannot get
the values using the dates, unless I use the exact same instance of Date
.
I've written a unit test with it:
it('Should not be a problem', () => {
let d1: Date = new Date(2019, 11, 25); // Christmas day
let d2: Date = new Date(2019, 11, 25); // Same Christmas day?
let m: Map<Date, string> = new Map<Date, string>();
m.set(d1, "X");
expect(m.get(d1)).toBe("X"); // <- This is OK.
expect(m.get(d2)).toBe("X"); // <- This test fails.
});
Why is it that I can't get the value from the map unless I use the exact same instance?
It will always be falsy because those two date objects are distinct mutable javascript objects.
new Date(2019, 11, 25) === new Date(2019, 11, 25) ==> false
.
You can look at answers in this post.
This is core logic of Map
, as you know map stores value in key value pair.
For the comparison of keys, the keys should always be of same reference. As you might know that string literal references are equal in many programming languages, hence use of string is preferred as key in map.
The above behavior is not only true for date
but it is true for any other mutable object type.
e.g.
let str1 = 'test';
let str2 = 'test';
str1 == str2; // true
let str1 = new String('test');
let str2 = new String('test');
str1 == str2; // false
While getting value from map
, the keys data is not considered, rather the unique identity of key is search. And when you create immutable object each object may have same data but the references of each object will be different. Hence it will be treated as different keys.
The solution is use types which can have same references throughout program, such as string literals.
One more example,
class Demo {
field: string;
constructor(f: string) {
this.field = f;
}
}
const d1 = new Demo('test');
const d2 = new Demo('test');
// both objects seems same by data, but there references are different
// hence will be treated as separate keys.
const m: Map<any, string> = new Map<any, string>();
m.set(d1, 'value1');
console.log(m.get(d1)); // value1
console.log(m.get(d2)); // undefined
Better use primitive values (number, string) as Map keys:
let m: Map<string, string> = new Map<string, string>();
let d1: Date = new Date(2019, 11, 25); // Christmas day
let d2: Date = new Date(2019, 11, 25); // Same Christmas day?
m.set(d1.toDateString(), "X");
console.log(d1.toDateString())
console.log(d2.toDateString())
console.log(m.get(d2.toDateString()))
I provided a link to such behaviour on the above comments.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With