How to serialize a Map in javascript? - javascript

So...there is this type in js called Map and it is really nice...it is faster than an Object for iterations and calculations so I really like it. However...you can't pass Maps around as you could with objects.
I know that I could turn Map into JSON but it is costly to do so and it kind of looses the point of using Maps in the first place.
JSON format is not meant for Maps...it was meant for Objects.
So...lets move from the JSON format a little.
Is there a way for me to serialize a Map into a string in any way so that I can then do the opposite - from said serialized Map to end up with a Map
Preferably this method should be as easy to perform as JSON.stringify or its counterpart JSON.parse.
I want to use Map as it is faster and better but I need to send my data as string. The format of the string is not important as long as I can parse it back into a Map

-- edit: Added the missing JSON Stringify function during serialization -
There is a native way of doing this in Javascript.
the Map object has a method called entries() that returns an iterator that will yield each entry in the map as an array with 2 values in it. It will look like [key, value].
To avoid writing any looping code yourself, you can use Array.from() which can consume an iterator and extract each item from it. Having that, the following will give you your Map object in a serialized form.
let mySerialMap = JSON.stringify(Array.from(myMap.entries()))
console.log(mySerialMap)
Now, that's not even the coolest part. But before getting there, let me show you one possible way of using the Map constructor.
let original = new Map([
['key1', 'the-one-value']
['second-key, 'value-two']
])
The array of array that you can see being passed to the Map object is in the same format as what you get from using Array.from(myMap.entries()).
So you can rebuild you map in a single line using the following sample code:
let myMap = new Map(JSON.parse(mySerialMap))
And you can use myMap as you would any Map.
let myMap = new Map(JSON.parse(mySerialMap))
let first = myMap.get('some-key')
myMap.set('another-key', 'some value')

I guess the whole point of Maps/Dictionaries is that you can use objects as keys in them, so:
let a = {}, b = {}, m = new Map();
m.set(a,b);
m.get(a); // b
So you get b since you have a reference on a. Let's say you serialize the Map by creating an Array of arrays, and stringify that to json:
function serialize (map) {
return JSON.stringify([...map.entries()])
}
let s = serialize(m); // '[[{}, {}]]'
// '[[<key>, <val>], … ]'
Than you could:
let m2 = JSON.parse(s).reduce((m, [key, val])=> m.set(key, val) , new Map());
But the question now is: How to get a certain key? Since there does not exist any reference, due to the fact that all objects have been stringified and recreated, it is not possible to query a dedicated key.
So all that would only work for String keys, but that really takes most of power of maps, or in other words, reduces them to simple objects, what is the reason maps were implemented.

To #philipp's point, people who care about the serialization of the Map will probably prefer objects (leverages intuition, reduces '[]' arithmetic). Object.entries() and Object.fromEntries() can make that a bit more literate:
writeMe = new Map()
writeMe.set('a', [1])
writeMe.set('b', {myObjValue: 2})
// ▶ Map(2) {'a' => Array(1), 'b' => {…}}
written = JSON.stringify(Object.fromEntries(writeMe.entries()))
// '{"a":[1],"b":{"myObjValue":2}}'
read = new Map(Object.entries(JSON.parse(written)))
// ▶ Map(2) {'a' => Array(1), 'b' => {…}}
read.get("b")
// ▶ {myObjValue: 2}

Related

Using ECMA6 Set as object key

Is there a way to use Set as object keys
let x = {}
const a = new Set([3, 5])
x[a] = 1
console.log(x) // >{[object Set]: 1}
const b = new Set([1, 4])
x[b] = 2
console.log(x) // >{[object Set]: 2}
The keys are being overwritten even though the sets are not equal.
Thanks!
No this is not possible because Object keys must be strings or symbols. If you would like to use a Set as a key you can try using a Map. Maps are similar to objects except you can use other objects as keys for a map.
One thing to keep in mind is that you cannot use maps exactly like you use Objects.
This is directly from the Mozilla docs.
The following IS NOT A VALID USE OF A MAP.
let wrongMap = new Map()
wrongMap['bla'] = 'blaa'
wrongMap['bla2'] = 'blaaa2'
console.log(wrongMap) // Map { bla: 'blaa', bla2: 'blaaa2' }
But that way of setting a property does not interact with the Map data structure. It uses the feature of the generic object. The value of 'bla' is not stored in the Map for queries. Other operations on the data fail:
Correct use of a map looks like the below:
let map = new Map()
// setting values
map.set(key, value)
// getting values
map.get(key)
Remember that if you use an Object like a Set as a key, the reference of the Set is what matters.
If you instantiate two sets separately, even if they both have the same contents, they will have different references and be considered different keys.
Do you mean that the map in ES6 like this:
x = new Map()
a = new Set([3, 5])
x.set(a, 1)
console.log(x);

How to append values to ES6 Map

I am trying to learn about ES6 Map data structures and am having difficulty understanding some of their behaviour. I would like to create a Map with an Array as a value and append (push) new values onto the current value of the Map. For example:
let m = new Map()
m.set(1, []) // []
m.set(1, m.get(1).push(2)) // [[1, 1]]
I am confused as to why I do not get [2] as the value of m.get(1) above. How can I append values to the array in my map?
That's because the method push returns the size of the array after the insertion.
You can change your code to the following to append to an array:
m.get(1).push(2);
And it'll update the value in the map, there's no need to try to re-set the value again as the value is passed back as reference.
The best way to define a Map according to your need is to explicitly tell the Map, what kind of data you want to deal with.
in your case you want values in an array, we could get using a "string id" for example
In this case you will have this :
let map = new Map<String, Array<any>>
Then you can create items like map["key"] = ["lol", 1, null]
There is two thing. First as #Adriani6 said the push method do not returns a pointer to the array but the size of the array.
Secondly, you do not need to do an other m.set, because your push will affect directly the array behind the reference returned by m.get
function displayMap(m) {
m.forEach(function(val, key) {
console.log(key + " => " + val);
});
}
let m = new Map();
m.set(1, []);
displayMap(m);
m.get(1).push(20);
displayMap(m);
It fails, because the return of push() is the size of the array after push.
You can push the content after doing a get().
m.get(1).push(2);
If you want to test set() then write a a self executable function like this:
let m = new Map()
m.set(1, []) // []
console.log(m.get(1))
m.set(1, (() => {m.get(1).push(2);return m.get(1);})());
console.log(m.get(1))
Here's a working example of what you are trying to do (open console)
Have a look here. As you can see the push method returns the new length of the array you just mutated, hence your result.

Reversing an array which is a value in object in Javascript

I am trying to reverse an array which is an element in an object.
colorKey = {
"2m":["#ffffff","#000000"]
}
colorKey["2mi"] = colorKey["2m"];
Array.reverse(colorKey["2mi"])
This is not working and returning colorKey["2mi"] the same as colorKey["2m"]. When I run the same command in developer console in browser, it reverses successfully. Where is the problem?
This is no static method off Array called reverse. reverse is an instance method (Array.prototype.reverse) off the Array object, so the instance of the Array must be the caller.
This solves your problem:
colorKey = {
"2m":["#ffffff","#000000"]
}
colorKey["2mi"] = colorKey["2m"];
colorKey["2mi"].reverse();
Output:
["#000000", "#ffffff"]
Calling reverse() for an array mutates it (reverse is in place - a new array is not created). What you want, apparently, is to have a reversed copy of the array. Basically, create a new array with the same values and then reverse it.
var a = [1, 2], b;
b = a.slice(0).reverse();
Slice creates a new array with the same elements (but remember that it is not cloning the elements).
#Rajat Aggarwal
What you are asking for, is to clone your previous array in reverse order.
The only trivial part of it would be reversing it. Because there is no way of cloning Objects and Arrays, nor a general method that you could write down as a function to be using it universally.
This specific array from the sample you provided can be cloned easily because it is flat and it only contains primitives. But the solution to it, will be exactly as specific as the sample array provided.
A specific solution to this task would be to use a plain coma-separated string of successive values and convert that to specific arrays of their corresponding primitive values.:
var colors = "#ffffff,#000000";
var colorKey = {
"2m":colors.split(","),
"2mi":colors.split(",").reverse()
}
which will yield you a:
>> colorKey
{
2m : #ffffff,#000000,
2mi : #000000,#ffffff
}

Javascript pushing objects into array changes entire array

I'm using a specific game making framework but I think the question applies to javascript
I was trying to make a narration script so the player can see "The orc hits you." at the bottom of his screen. I wanted to show the last 4 messages at one time and possibly allow the player to look back to see 30-50 messages in a log if they want. To do this I set up and object and an array to push the objects into.
So I set up some variables like this initially...
servermessage: {"color1":"yellow", "color2":"white", "message1":"", "message2":""},
servermessagelist: new Array(),
and when I use this command (below) multiple times with different data called by an event by manipulating servermessage.color1 ... .message1 etc...
servermessagelist.push(servermessage)
it overwrites the entire array with copies of that data... any idea why or what I can do about it.
So if I push color1 "RED" and message1 "Rover".. the data is correct then if I push
color1"yellow" and message1 "Bus" the data is two copies of .color1:"yellow" .message1:"Bus"
When you push servermessage into servermessagelist you're really (more or less) pushing a reference to that object. So any changes made to servermessage are reflected everywhere you have a reference to it. It sounds like what you want to do is push a clone of the object into the list.
Declare a function as follows:
function cloneMessage(servermessage) {
var clone ={};
for( var key in servermessage ){
if(servermessage.hasOwnProperty(key)) //ensure not adding inherited props
clone[key]=servermessage[key];
}
return clone;
}
Then everytime you want to push a message into the list do:
servermessagelist.push( cloneMessage(servermessage) );
When you add the object to the array, it's only a reference to the object that is added. The object is not copied by adding it to the array. So, when you later change the object and add it to the array again, you just have an array with several references to the same object.
Create a new object for each addition to the array:
servermessage = {"color1":"yellow", "color2":"white", "message1":"", "message2":""};
servermessagelist.push(servermessage);
servermessage = {"color1":"green", "color2":"red", "message1":"", "message2":"nice work"};
servermessagelist.push(servermessage);
There are two ways to use deep copy the object before pushing it into the array.
1. create new object by object method and then push it.
servermessagelist = [];
servermessagelist.push(Object.assign({}, servermessage));
Create an new reference of object by JSON stringigy method and push it with parse method.
servermessagelist = [];
servermessagelist.push(JSON.parse(JSON.stringify(servermessage));
This method is useful for nested objects.
servermessagelist: new Array() empties the array every time it's executed. Only execute that code once when you originally initialize the array.
I also had same issue. I had bit complex object that I was pushing in to the array. What I did; I Convert JSON object as String using JSON.stringify() and push in to the Array.
When it is returning from the array I just convert that String to JSON object using JSON.parse().
This is working fine for me though it is bit far more round solution.
Post here If you guys having alternative options
I do not know why a JSON way of doing this has not been suggested yet.
You can first stringify the object and then parse it again to get a copy of the object.
let uniqueArr = [];
let referencesArr = [];
let obj = {a: 1, b:2};
uniqueArr.push(JSON.parse(JSON.stringify(obj)));
referencesArr.push(obj);
obj.a = 3;
obj.c = 5;
uniqueArr.push(JSON.parse(JSON.stringify(obj)));
referencesArr.push(obj);
//You can see the differences in the console logs
console.log(uniqueArr);
console.log(referencesArr);
This solution also work on the object containing nested keys.
Before pushing, stringify the obj by
JSON.stringify(obj)
And when you are using, parse by
JSON.parse(obj);
As mentioned multiple times above, the easiest way of doing this would be making it a string and converting it back to JSON Object.
this.<JSONObjectArray>.push(JSON.parse(JSON.stringify(<JSONObject>)));
Works like a charm.

Convert a javascript associative array into json object using stringify and vice versa

I have a javascript associative array like one below
var my_cars= new Array()
my_cars["cool"]="Mustang";
my_cars["family"]="Station Wagon";
my_cars["big"]="SUV";
I want to convert it using Stringify to json object. I want to know after conversion how the json object will look like.
Also when i have this object How I can convert it back to associative array again.
First of all, by making my_cars an array and stringifying it, you don't get what you expect.
var my_cars= new Array()
my_cars["cool"]="Mustang";
my_cars["family"]="Station Wagon";
my_cars["big"]="SUV";
alert(JSON.stringify(my_cars));
This alerts [].
What you want is to start with {}:
var my_cars= {};
my_cars["cool"]="Mustang";
my_cars["family"]="Station Wagon";
my_cars["big"]="SUV";
alert(JSON.stringify(my_cars));
This alerts
{"cool":"Mustang","family":"Station Wagon","big":"SUV"}
To get your object back from the string, use JSON.parse().
var s = JSON.stringify(my_cars);
var c = JSON.parse(s);
alert(c.cool);
This alerts "Mustang".
See http://jsfiddle.net/Y2De9/
No,But the user want to use array not json.
Normal JavaScript arrays are designed to hold data with numeric indexes. You can stuff named keys on to them (and this can be useful when you want to store metadata about an array which holds normal, ordered, numerically indexed data), but that isn't what they are designed for. The JSON array data type cannot have named keys on an array.
If you want named keys, use an Object, not an Array.
*source
var test = []; // Object
test[0] = 'test'; //this will be stringified
Now if you want key value pair inside the array
test[1] = {}; // Array
test[1]['b']='item';
var json = JSON.stringify(test);
output
"["test",{"b":"item"}]"
so you can use an index with array,so alternatively
var my_cars= [];
my_cars[0]={};
my_cars[0]["cool"]="Mustang";
my_cars[1]={};
my_cars[1]["family"]="Station Wagon";
my_cars[2]={};
my_cars[2]["big"]="SUV";
console.log(JSON.stringify(my_cars));
Output
"[{"cool":"Mustang"},{"family":"Station Wagon"},{"big":"SUV"}]"
Moving my comment into an answer so I can show you a code example.
These types of array are no-no's in javascript. You should ONLY use an object for non-numeric keys like this. Array indexes should be numbers. Javascript objects can use arbitrary values for keys (like in your example). Arrays happen to "appear" to work because Arrays themselves are objects, but you will not find normal Array methods will work on them. For example, look at this code example.
var my_cars= new Array()
my_cars["cool"]="Mustang";
my_cars["family"]="Station Wagon";
my_cars["big"]="SUV";
alert(my_cars.length); // alerts 0
You have only added properties to the underlying object, not actually added elements to the Array. You should use an Object for this, not an Array. Javascript does not actually have an Associative Array. It has an Object who's properties can often be used like one would use an Associate Array in other languages. But, it's an Object, not an Array.
"JavaScript does not support arrays with named indexes"
The most close state to an associative array is an array with entries converted to properties (as in your case), so I provide a solution for this exact case.
The fun thing is that Chrome's console makes it feel like an associative array: ["cool":"Mustang", "family":"Station Wagon", "big":"SUV"] (Check with F12)
NOTE: open browser's console before running the snippet
var my_cars= new Array()
my_cars["cool"]="Mustang";
my_cars["family"]="Station Wagon";
my_cars["big"]="SUV";
let toAssociative=(keys, values)=>
values.reduce((acc, cv)=>{
acc[acc.shift()]=cv
return acc;
}, keys)
let fromAssociative = (assArr)=>({...assArr})
let serialized = JSON.stringify(fromAssociative(my_cars))
let o = JSON.parse(serialized)
let restored = toAssociative(Object.keys(o) , Object.values(o))
//NOTE: Look at the browser's console before executing (not SO console)
console.log("orig:",my_cars)
//[cool: "Mustang", family: "Station Wagon", big: "SUV"]
console.log("serialized:",serialized)
//{"cool":"Mustang","family":"Station Wagon","big":"SUV"}
console.log("restored:",restored) //NOTE: look at the browser's console (F12)
//[cool: "Mustang", family: "Station Wagon", big: "SUV"]
If for some reason you cannot convert your array into object, for instance you are working on a big framework or legacy code that you dont want to touch and your job is only to add som feature which requires JSON API use, you should consider using JSON.stringify(json,function(k,v){}) version of the API.
In the function you can now decide what to do with value of key is of a specific type.

Categories

Resources