Code Buckets

Buckets of code

TypeScript

Extending TypeScript to serialise Map objects to JSON

The collection objects offered by ES6 such as Map and Set are indeed a marvel. One notable downside to them though is that they don’t serialise to JSON. For example

JSON.stringify(MyMapObject);

Results in

{}

Always – irrespective of what is in the map collection. Not great and a pain when we want to stash the map object in a cookie or local storage to be used later. It just doesn’t do it out of the box. Here I’m going to extend TypeScript to do this so I will never need to think about it again.

Serialising the Map object.

There are a number of different ways to get the Map object to serialise and deserialise in and out of JSON. This one relies on the contents of the Map object being serialisable. If they are then we can do the following

Serialise Map into JSON
function toJson(map) {
return JSON.stringify(Array.from(map.entries()));
}
Deserialise JSON into Map
function fromJson(jsonStr) {
return new Map(JSON.parse(jsonStr));
}

This works in JavaScript (ES6) and TypeScript – which after all is a superset of JavaScript.

Serialise the Map Object

It feels a bit clunky to have these methods hanging around in some utility library. What I want to do is extend the TypeScript Map class to have these. To do this go to a TypeScript definition file (*.d.ts) and enter the following

interface Map<K, V> {
toJson(): string;
}

This will extend our map object to have a toJson method. In a suitable library file place the implementation and associate with the prototype object of the Map object

Map.prototype.toJson = function toJson() {
return JSON.stringify(Array.from(this.entries()));
}

It’s not hugely intuitive why we need to extend the prototype object at this point. From the mozilla documention

All Map instances inherit from Map.prototype.

So if we want our new method on an instance of the Map object we need to extend the prototype object –  because Map instances all inherit from this.

This is then called thus

let jsonString = myMap.toJson();

Which can then be stashed away in local storage or the like.

Deserialise a JSON string into a Map object

We are going to do similar to implement the deserialisation part. It’s not quite the same though. If we are serialising a Map object it makes sense to have the toJson method associated with an instance of a Map object. If we are deserialising into a new Map object we want the method associated with the class- like a static method in C#. The following illustrates the difference

let myMap: Map<string, string> = new Map<string, string>();
myMap.set("key1", "value1")
myMap.set("key2", "value2")
myMap.set("key2", "value2")

//.. serialise method associated with the instance of the class
let jsonString = myMap.toJson();

//.. deserialise method associated with the class
let restoredMap: Map<string, string> = Map.fromJson(jsonString);

Again go to a TypeScript definition file (*.d.ts) and enter the following

interface MapConstructor {
fromJson(jsonStr: string);
}

Note – this time I am extending the MapConstructor object rather than the Map<K,V> object. In TypeScript the MapConstructor object returns a reference to the Map function that created the object. So by extending the MapConstructor Typescript object I am associating the method with the object that created the instance of the map object. I am associating the new method with the class and not the instance.

Now associate our implementation directly with the Map object which itself implements MapConstructor

Map.jsonToMap = function jsonToMap(jsonStr) {
return new Map(JSON.parse(jsonStr));
}

Which we call thus

let restoredMap: Map<string, string> = Map.jsonToMap(jsonString);

That’s it – the Map object now has toJson and fromJson methods which handle our JSON conversion. The Set object suffers from the same limitations and can be extended in the same way.

Useful Links

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
Map and Set objects are part of the ES6 standard. TypeScript offers a strongly typed version of these collections

https://codepunk.io/extending-strings-and-other-data-types-in-typescript/
More detail about extending built in types in TypeScript

https://stackoverflow.com/
The initial code to serialise and deserialise the Map object came from a stack overflow question i.e.

function toJson(map) {
return JSON.stringify(Array.from(map.entries()));
}

function fromJson(jsonStr) {
return new Map(JSON.parse(jsonStr));
}

but I’ve forgotten which one – if I find it I will pop the reference here in the interest of full disclosure and not ripping off someone else’s work and claiming it for my own.

https://codebuckets.com/2022/09/30/console_log-for-map-and-set/
I’ve an other blog post which compares the logging for Sets, Maps, Arrays and Objects which adds some additional context to this article.

1 COMMENTS

  1. Great content… thank you Tim

    But shouldn’t fromJson:
    interface MapConstructor {
    fromJson(jsonStr: string);
    }
    have a return type like this:
    interface MapConstructor {
    fromJson(jsonStr: string) Map;
    }

    anyway.. nice and easy, what I needed 🙂

LEAVE A RESPONSE

Your email address will not be published. Required fields are marked *