Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript: using Date as key in Map?

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?

like image 985
jmgonet Avatar asked Dec 15 '19 16:12

jmgonet


3 Answers

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.

like image 122
Julien Avatar answered Nov 02 '22 01:11

Julien


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
like image 4
Plochie Avatar answered Nov 02 '22 02:11

Plochie


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.

like image 3
KingDarBoja Avatar answered Nov 02 '22 03:11

KingDarBoja