How to convert a plain object into an ES6 Map? - javascript

For some reason I can't find this simple thing in the MDN docs (maybe I'm just missing it).
I expected this to work:
const map = new Map({foo: 'bar'});
map.get('foo'); // 'bar'
...but the first line throws TypeError: (var)[Symbol.iterator] is not a function
How do I make a Map from a plain object? Do I really have to first convert it into an array of arrays of key-value pairs?

Yes, the Map constructor takes an array of key-value pairs.
Object.entries is a new Object static method available in ES2017 (19.1.2.5).
const map = new Map(Object.entries({foo: 'bar'}));
map.get('foo'); // 'bar'
It's currently implemented in Firefox 46+ and Edge 14+ and newer versions of Chrome
If you need to support older environments and transpilation is not an option for you, use a polyfill, such as the one recommended by georg:
Object.entries = typeof Object.entries === 'function' ? Object.entries : obj => Object.keys(obj).map(k => [k, obj[k]]);

Do I really have to first convert it into an array of arrays of key-value pairs?
No, an iterator of key-value pair arrays is enough. You can use the following to avoid creating the intermediate array:
function* entries(obj) {
for (let key in obj)
yield [key, obj[key]];
}
const map = new Map(entries({foo: 'bar'}));
map.get('foo'); // 'bar'

The answer by Nils describes how to convert objects to maps, which I found very useful. However, the OP was also wondering where this information is in the MDN docs. While it may not have been there when the question was originally asked, it is now on the MDN page for Object.entries() under the heading Converting an Object to a Map which states:
Converting an Object to a Map
The new Map() constructor accepts an iterable of entries. With Object.entries, you can easily convert from Object to Map:
const obj = { foo: 'bar', baz: 42 };
const map = new Map(Object.entries(obj));
console.log(map); // Map { foo: "bar", baz: 42 }

ES6
convert object to map:
const objToMap = (o) => new Map(Object.entries(o));
convert map to object:
const mapToObj = (m) => [...m].reduce( (o,v)=>{ o[v[0]] = v[1]; return o; },{} )
Note: the mapToObj function assumes map keys are strings (will fail otherwise)

const myMap = new Map(
Object
.keys(myObj)
.map(
key => [key, myObj[key]]
)
)

Alternatively you can use the lodash toPairs method:
const _ = require('lodash');
const map = new Map(_.toPairs({foo: 'bar'}));

Related

JS do not sort numeral object indices

When creating an object like so
let o = {}
o["prop2"] = "val"
o["prop1"] = "val"
The properties are stored in the order they are set, so when getting the keys of the object (Object.keys(o)) they are returned in their proper order (["prop2", "prop1"]). This order has wide reaching consequences as for...in loops use it and hence JSON.stringify is influenced by it as well. I want to use this behaviour for performance optimisations (like sending already sorted objects that are easier to parse).
However all this falls apart when using numeric indices. The seemingly similar object
let o = {}
o["2"] = "val"
o["1"] = "val"
does not confirm the behaviour above. Here Object.keys(o) returns ["1", "2"] :(.
Can this be avoided somehow? Without pre-/post fixing all properties?
Use a Map instead to retain insertion order.
let m = new Map;
m.set("2", "val");
m.set("1", "val");
m.forEach((v,k)=>console.log(k,"=",v));
To convert it to JSON, you can stringify the entries of the Map. You can use parse the string and pass the entries to the constructor of Map to convert it back.
const map = new Map([
['foo', 10],
['baz', 42]
]);
let str = JSON.stringify([...map.entries()])
console.log(str);
new Map(JSON.parse(str)).forEach((v,k)=>console.log(k,"=",v));
You can provide an callback to JSON.stringify and JSON.parse to convert nested Map structures.
let m = new Map;
m.set("b", new Map([["c","d"],["e","f"], ["d", new Map([["a", 1]])]]));
let str = JSON.stringify(m, (key,val)=>{
return Object.prototype.toString.call(val) === '[object Map]' ? [...val.entries()] : val;
});
console.log(str);
let m2 = JSON.parse(str, (key, val)=>{
return Array.isArray(val) && Array.isArray(val[0]) ? new Map(val) : val;
});
console.log(m2);
Demo: https://jsfiddle.net/ob8d2uLf/1/

Javascript: Convert a JSON string into ES6 map or other to preserve the order of keys

Is there a native (built in) in ES6 (or subsequent versions), Javascript or in TypeScript method to convert a JSON string to ES6 map OR a self-made parser to be implemented is the option? The goal is to preserve the order of the keys of the JSON string-encoded object.
Note: I deliberately don't use the word "parse" to avoid converting a JSON string first to ECMA script / JavaScript object which by definition has no order of its keys.
For example:
{"b": "bar", "a": "foo" } // <-- This is how the JSON string looks
I need:
{ b: "bar", a: "foo" } // <-- desired (map version of it)
UPDATE
https://jsbin.com/kiqeneluzi/1/edit?js,console
The only thing that I do differently is to get the keys with regex to maintain the order
let j = "{\"b\": \"bar\", \"a\": \"foo\", \"1\": \"value\"}"
let js = JSON.parse(j)
// Get the keys and maintain the order
let myRegex = /\"([^"]+)":/g;
let keys = []
while ((m = myRegex.exec(j)) !== null) {
keys.push(m[1])
}
// Transform each key to an object
let res = keys.reduce(function (acc, curr) {
acc.push({
[curr]: js[curr]
});
return acc
}, []);
console.log(res)
ORIGINAL
If I understand what you're trying to achieve for option 2. Here's what I came up with.
https://jsbin.com/pocisocoya/1/edit?js,console
let j = "{\"b\": \"bar\", \"a\": \"foo\"}"
let js = JSON.parse(j)
let res = Object.keys(js).reduce(function (acc, curr) {
acc.push({
[curr]: js[curr]
});
return acc
}, []);
console.log(res)
Basically get all the keys of the object, and then reduce it. What the reducer function convert each keys to an object
function jsonToMap(jsonStr) {
return new Map(JSON.parse(jsonStr));
}
More details : http://2ality.com/2015/08/es6-map-json.html
use for in loop
let map = new Map();
let jsonObj = {a:'a',b:'b',c:'c'}
for (let i in jsonObj){
map.set(i,jsonObj[i]);
}
btw, i saw the comment below and i think map is not ordered because you use key to achieve data in map, not the index.

Lodash how to assign specific property of object during comparing two collections of different objects

I have one question. Is there any function in lodash library which is going to provide me the method for comparing each object in both collections by specific property and if the condition is fulfilled then creating another object?
example:
a) [{a:1,b:'abc',c:'dfr'},{a:3,b:'dfe',c:'gty'}....{}]
b) [{a:3,b:'fgt',d:'ghr'},{a:5,b:'ghk',d:'bhj'}...{}]
result:[{a:3,b:'dfe',c:'gty',d:'ghr'}]
I would like to compare these two collections by 'a' parameter and if a parameter is matched then assign parameter 'd' to object from the collection a). I have read something about differenceWith or intersection, but I am not sure if it may work, or maybe there are better functions to do this. Thanks in advance!
Create a Map of the items in array2 by their keys. Iterate array1 with Array.filter(), and remove all items that their a property is not found in the Map. Use Array.map() to combine the remaining items with d property of their counterpart in the Map:
const array1 = [{a:1,b:'abc',c:'dfr'},{a:3,b:'dfe',c:'gty'}];
const array2 = [{a:3,b:'fgt',d:'ghr'},{a:5,b:'ghk',d:'bhj'}];
// create a Map of array2 items by the a property
const array2Map = new Map(array2.map((o) => [o.a, o]));
const result = array1
// filter out all items that don't have matching item in the Map
.filter(o => array2Map.has(o.a))
// map the items, and add the d property from the item in the Map
.map(o => ({
...o,
d: array2Map.get(o.a).d
}));
console.log(result);
1) Native way: doing array map and comparing inside map loop with assigning objects.
const array1 = [{a:1,b:'abc',c:'dfr'},{a:3,b:'dfe',c:'gty'}];
const array2 = [{a:3,b:'fgt',d:'ghr'},{a:5,b:'ghk',d:'bhj'}];
const result = array1.map(obj1 => {
const obj2 = array2.find(obj => obj.a === obj1.a);
if (obj2) return Object.assign({}, obj1, {d: obj2.d});
}).filter(value => value);
console.log(result);
2) Lodash way: same as in example 1 but using only lodash methods
const array1 = [{a:1,b:'abc',c:'dfr'},{a:3,b:'dfe',c:'gty'}];
const array2 = [{a:3,b:'fgt',d:'ghr'},{a:5,b:'ghk',d:'bhj'}];
const result = _.compact(
_.map(array1, obj1 => {
const obj2 = _.find(array2, _.matchesProperty('a', obj1.a));
if (obj2) return _.assign({}, obj1, _.pick(obj2, ['d']));
})
);
console.log(result);
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.10/lodash.min.js"></script>
P.S. Try to avoid lodash, underscore and etc stuff as much as possible. JS, ES are rich enough.

Why does a js map on an array modify the original array?

I'm quite confused by the behavior of map().
I have an array of objects like this:
const products = [{
...,
'productType' = 'premium',
...
}, ...]
And I'm passing this array to a function that should return the same array but with all product made free:
[{
...,
'productType' = 'free',
...
}, ...]
The function is:
const freeProduct = function(products){
return products.map(x => x.productType = "free")
}
Which returns the following array:
["free", "free", ...]
So I rewrote my function to be:
const freeProduct = function(products){
return products.map(x => {x.productType = "free"; return x})
}
Which returns the array as intended.
BUT ! And that's the moment where I loose my mind, in both cases my original products array is modified.
Documentation around map() says that it shouldn't ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map ).
I even tried to create a clone of my array turning my function into this:
const freeProduct = function(products){
p = products.splice()
return p.map(x => {x.productType = "free"; return x})
}
But I still get the same result (which starts to drive me crazy).
I would be very thankful to anyone who can explain me what I'm doing wrong!
Thank you.
You're not modifying your original array. You're modifying the objects in the array. If you want to avoid mutating the objects in your array, you can use Object.assign to create a new object with the original's properties plus any changes you need:
const freeProduct = function(products) {
return products.map(x => {
return Object.assign({}, x, {
productType: "free"
});
});
};
2018 Edit:
In most browsers you can now use the object spread syntax instead of Object.assign to accomplish this:
const freeProduct = function(products) {
return products.map(x => {
return {
...x,
productType: "free"
};
});
};
To elaborate on SimpleJ's answer - if you were to === the two arrays, you would find that they would not be equal (not same address in memory) confirming that the mapped array is in fact a new array. The issue is that you're returning a new array, that is full of references to the SAME objects in the original array (it's not returning new object literals, it's returning references to the same object). So you need to be creating new objects that are copies of the old objects - ie, w/ the Object.assign example given by SimpleJ.
Unfortunately, whether the spread operator nor the object assign operator does a deep copy.... You need to use a lodash like function to get areal copy not just a reference copy.
const util = require('util');
const print = (...val) => {
console.log(util.inspect(val, false, null, false /* enable colors */));
};
const _ = require('lodash');
const obj1 = {foo:{bar:[{foo:3}]}};
const obj2 = {foo:{bar:[{foo:3}]}};
const array = [obj1, obj2];
const objAssignCopy = x => { return Object.assign({}, x, {})};
const spreadCopy = x => { return {...x}};
const _Copy = x => _.cloneDeep(x);
const map1 = array.map(objAssignCopy);
const map2 = array.map(spreadCopy);
const map3 = array.map(_Copy);
print('map1', map1);
print('map2', map2);
print('map3', map3);
obj2.foo.bar[0].foo = "foobar";
print('map1 after manipulation of obj2', map1); // value changed
print('map2 after manipulation of obj2', map2); // value changed
print('map3 after manipulation of obj2', map3); // value hasn't changed!
Array Iterator Array.map() creates the new array with the same number of elements or does not change the original array. There might be the problem with referencing if there is object inside the array as it copies the same reference, so, when you are making any changes on the property of the object it will change the original value of the element which holds the same reference.
The solution would be to copy the object, well, array.Splice() and [...array](spread Operator) would not help in this case, you can use JavaScript Utility library like Loadash or just use below mention code:
const newList = JSON.parse(JSON.stringify(orinalArr))
Array Destructuring assignment can be used to clone the object.
const freeProduct = function(products){
p = products.splice()
return p.map(({...x}) => {x.productType = "free"; return x})
}
This method will not modify the original object.

First item from a Map on JavaScript ES2015

I have a Map like this:
const m = new Map();
m.set('key1', {})
.
m.set('keyN' {})
the Mapcan have 1 or many items. Can I get the first item by index, without m.get('key1') and without a iterator loop?
like: m.get()[0]
Use the Map.prototype.entries function, like this
const m = new Map();
m.set('key1', {})
m.set('keyN', {})
console.log(m.entries().next().value); // [ 'key1', {} ]
If you want to get the first key, then use Map.prototype.keys, like this
console.log(m.keys().next().value); // key1
Similarly if you want to get the first value, then you can use Map.prototype.values, like this
console.log(m.values().next().value); // {}
The reason why we have to call next() on the returned values is that, all those functions return iterators. Read more about the iteration protocol here.
For the specific example you are wondering about, destructuring would be perfect.
let m = new Map();
m.set('key1', {});
m.set('key2', {});
let [[, obj]] = m;
e.g.
let [pair] = m;
let [key, obj] = pair;
is one option to destructure and then grab the value, but the easier option would be
let [obj] = m.values();
It could also be done using the spread feature at ES6 and the next versions. Let's declare a new Map variable, then add two values.
After that, we will use ... to convert the map into array or you can use Array.from then to get the first element just use [0] on the gotten array.
const m = new Map();
m.set('key1', 1);
m.set('key2', 2);
console.log([...m][0]); // ['key1', 1] 👍🏼
Or quickly by using distruct feature for javascript array, so that [k, v] array refers to first item at the map.
const [[k, v]] = m;
console.log(k, v); // 'key1', 1
Also, that is correct for both Set and Map: you can convert anything to Array and then get any element by its index. Something like this:
const m = new Map();
m.set('key1', {});
m.set('key2', {});
console.log(Array.from(m)[0]); // ['key1', {}]
For all iterable objects you can use the iterator object[Symbol.iterator]().
In our case this will point to the entries() method as explained in the above MDN page :
The map iterator function, which is the entries() function by default.
const m = new Map();
m.set('key1', {})
m.set('keyN', {})
console.log(m[Symbol.iterator]().next().value); // [ 'key1', {} ]
And here is a benchmark of all solutions :
https://jsbench.me/9fkpm6q9y0/1
The entries() version wins but it is very tight to the iterator version. This is logical since [Symbol.iterator]() calls entries().

Categories

Resources