0

I would like to extend the TypeScript Map type in my react TypeScript app to allow me to use .map(), in a similar way as you might with an Array.

I found this post that describes several methods, but the most natural to me for TypeScript is the answer by Jeremy Lachkar, although it isn't very popular. It means I don't have to introduce any awkward syntax in the client code and can just extend the Map object.

His code looks like this:

export {}

declare global {
    interface Map<K, V> {
        map<T>(predicate: (key: K, value: V) => T): Map<V, T>
    }
}

Map.prototype.map = function<K, V, T>(predicate: (value: V, key: K) => T): Map<K, T> {
    let map: Map<K, T> = new Map()

    this.forEach((value: V, key: K) => {
        map.set(key, predicate(value, key))
    })
    return map
}

I have augmented this to make it, for me, what seems more natural (I think the Map<V, T> in the interface might be a mistake in any case):

export {}

declare global {
  interface Map<K, V> {
    map<T>(predicate: (key: K, value: V) => T): Map<K, T>;
  }
}

Map.prototype.map = function<K, V, T>(predicate: (key: K, value: V) => T): Map<K, T> {
  let map: Map<K, T> = new Map();

  this.forEach((key: K, value: V) => {
    map.set(key, predicate(key, value));
  });

  return map;
}

This works great. I can now map over my Map and produce a new one. However, now none of the other methods on Map, such as .set(), are available to me, and my code throws errors. These are now undefined.

Any idea how to deal with this? I'm imagining in my little head that this might be a tsconfig.json issue of some sort, but I don't know.

serlingpa
  • 12,024
  • 24
  • 80
  • 130
  • [Don't extend builtin prototypes](https://stackoverflow.com/questions/14034180/why-is-extending-native-objects-a-bad-practice). Map already implements a `forEach` and you can very easily iterate the entries via `Map.entries()`. – pilchard Nov 30 '22 at 14:03

2 Answers2

0
class Map2<K, V> extends Map<K, V> {
  map<T>(mapper: (value: V, key: K, map: this) => T): Map2<K, T> {
    let m = new Map2<K, T>;
    for (let [k, v] of this.entries()) {
      m.set(k, mapper(v, k, this))
    }
    return m;
  }
}

let m = new Map2([[1, 2], [3, 4]])
console.log(m)

let m2 = m.map(e => e + 10)
console.log(m2)

Making a subclass is generally a better idea then changing prototypes (tho that works well if you are the only one who changes prototypes)

declare global {
  interface Map<K, V> {
    map1<T>(mapper: (value: V, key: K, map: this) => T): Map2<K, T>
  }
}

Map.prototype.map1 = Map2.prototype.map

export {}
Dimava
  • 7,654
  • 1
  • 9
  • 24
  • Hmm…am I to understand that I have to use a different name for my class? I mean, I could do that pretty easily, but I’d rather avoid it. Can’t I extend the class with an “extension” in the same way I might in, say, C#? – serlingpa Nov 30 '22 at 13:50
  • I am not at my computer right now, but wouldn’t that second block of code suffer from the same problem? That the existing methods would be made unavailable? – serlingpa Nov 30 '22 at 13:53
  • "extension" is modifying prototype, which you already did, and I did – Dimava Nov 30 '22 at 14:19
  • Honestly I dunno why that didnt work for you, it works fine for me in tsplay.dev – Dimava Nov 30 '22 at 14:23
  • Well, have you tried .set() on Map? Not Map2, but Map? I don’t really want to create a new class, just extend the existing one. – serlingpa Nov 30 '22 at 14:26
  • Well, you can, that's same, just has different angles ot clearness. set does work, see https://tsplay.dev/WylbZm – Dimava Nov 30 '22 at 14:28
  • Thanks for your efforts, but the code doesn't work. Logging out `m1` at the end of your code snippet (after changing the module to CommonJS) yields an empty object. I'm just going to subclass and be done with it. – serlingpa Nov 30 '22 at 14:58
0

Ok, turns out it was my client code that was wrong, and that is why the .set() and so on were undefined. So the original post I referenced right at the top does it fact work.

I had imagined it was because redefining the interface had removed the other methods. Not so. Silly me.

As you were.

serlingpa
  • 12,024
  • 24
  • 80
  • 130