Is it possible to sort the entries of a es6 map object?
var map = new Map(); map.set('2-1', foo); map.set('0-1', bar); results in:
map.entries = { 0: {"2-1", foo }, 1: {"0-1", bar } } Is it possible to sort the entries based on their keys?
map.entries = { 0: {"0-1", bar }, 1: {"2-1", foo } } 314 Answers
According MDN documentation:
A Map object iterates its elements in insertion order.
You could do it this way:
var map = new Map(); map.set('2-1', "foo"); map.set('0-1', "bar"); map.set('3-1', "baz"); var mapAsc = new Map([...map.entries()].sort()); console.log(mapAsc)Using .sort(), remember that the array is sorted according to each character's Unicode code point value, according to the string conversion of each element. So 2-1, 0-1, 3-1 will be sorted correctly.
Short answer
new Map([...map].sort((a, b) => // Some sort function comparing keys with a[0] b[0] or values with a[1] b[1] )) If you're expecting strings: As normal for .sort you need to return -1 if lower and 0 if equal; for strings, the recommended way is using .localeCompare() which does this correctly and automatically handles awkward characters like ä where the position varies by user locale.
So here's a simple way to sort a map by string keys:
new Map([...map].sort((a, b) => String(a[0]).localeCompare(b[0]))) ...and by string values:
new Map([...map].sort((a, b) => String(a[1]).localeCompare(b[1]))) These are type-safe in that they won't throw an error if they hit a non-string key or value. The String() at the start forces a to be a string (and is good for readability), and .localeCompare() itself forces its argument to be a string without hitting an error.
In detail with examples
tldr: ...map.entries() is redundant, just ...map is fine; and a lazy .sort() without passing a sort function risks weird edge case bugs caused by string coercion.
The .entries() in [...map.entries()] (suggested in many answers) is redundant, probably adding an extra iteration of the map unless the JS engine optimises that away for you.
In the simple test case, you can do what the question asks for with:
new Map([...map].sort()) ...which, if the keys are all strings, compares squashed and coerced comma-joined key-value strings like '2-1,foo' and '0-1,[object Object]', returning a new Map with the new insertion order:
Note: if you see only {} in SO's console output, look in your real browser console
const map = new Map([ ['2-1', 'foo'], ['0-1', { bar: 'bar' }], ['3-5', () => 'fuz'], ['3-2', [ 'baz' ]] ]) console.log(new Map([...map].sort()))HOWEVER, it's not a good practice to rely on coercion and stringification like this. You can get surprises like:
const map = new Map([ ['2', '3,buh?'], ['2,1', 'foo'], ['0,1', { bar: 'bar' }], ['3,5', () => 'fuz'], ['3,2', [ 'baz' ]], ]) // Compares '2,3,buh?' with '2,1,foo' // Therefore sorts ['2', '3,buh?'] ******AFTER****** ['2,1', 'foo'] console.log('Buh?', new Map([...map].sort())) // Let's see exactly what each iteration is using as its comparator for (const iteration of map) { console.log(iteration.toString()) }Bugs like this are really hard to debug - don't risk it!
If you want to sort on keys or values, it's best to access them explicitly with a[0] and b[0] in the sort function, like above; or with array destructuring in the function arguments:
const map = new Map([ ['2,1', 'this is overwritten'], ['2,1', '0,1'], ['0,1', '2,1'], ['2,2', '3,5'], ['3,5', '2,1'], ['2', ',9,9'] ]) // Examples using array destructuring. We're saying 'keys' and 'values' // in the function names so it's clear and readable what the intent is. const sortStringKeys = ([a], [b]) => String(a).localeCompare(b) const sortStringValues = ([,a], [,b]) => String(a).localeCompare(b) console.log('By keys:', new Map([...map].sort(sortStringKeys))) console.log('By values:', new Map([...map].sort(sortStringValues)))And if you need a different comparison than alphabetical order of strings, don't forget to always make sure you return -1 and 1 for before and after, not false or 0 as with raw a[0] > b[0] because that is treated as equals.
Convert Map to an array using Array.from, sort array, convert back to Map, e.g.
new Map( Array .from(eventsByDate) .sort((a, b) => { // a[0], b[0] is the key of the map return a[0] - b[0]; }) ) 2The idea is to extract the keys of your map into an array. Sort this array. Then iterate over this sorted array, get its value pair from the unsorted map and put them into a new map. The new map will be in sorted order. The code below is it's implementation:
var unsortedMap = new Map(); unsortedMap.set('2-1', 'foo'); unsortedMap.set('0-1', 'bar'); // Initialize your keys array var keys = []; // Initialize your sorted maps object var sortedMap = new Map(); // Put keys in Array unsortedMap.forEach(function callback(value, key, map) { keys.push(key); }); // Sort keys array and go through them to put in and put them in sorted map keys.sort().map(function(key) { sortedMap.set(key, unsortedMap.get(key)); }); // View your sorted map console.log(sortedMap); 1You can convert to an array and call array soring methods on it:
[...map].sort(/* etc */); 2Unfortunately, not really implemented in ES6. You have this feature with OrderedMap.sort() from ImmutableJS or _.sortBy() from Lodash.
One way is to get the entries array, sort it, and then create a new Map with the sorted array:
let ar = [...myMap.entries()]; sortedArray = ar.sort(); sortedMap = new Map(sortedArray); But if you don't want to create a new object, but to work on the same one, you can do something like this:
// Get an array of the keys and sort them let keys = [...myMap.keys()]; sortedKeys = keys.sort(); sortedKeys.forEach((key)=>{ // Delete the element and set it again at the end const value = this.get(key); this.delete(key); this.set(key,value); }) The snippet below sorts given map by its keys and maps the keys to key-value objects again. I used localeCompare function since my map was string->string object map.
var hash = {'x': 'xx', 't': 'tt', 'y': 'yy'}; Object.keys(hash).sort((a, b) => a.localeCompare(b)).map(function (i) { var o = {}; o[i] = hash[i]; return o; }); result: [{t:'tt'}, {x:'xx'}, {y: 'yy'}];
2 hours spent to get into details.
Note that the answer for question is already given at
However, the question has keys that are not usual ones,
A clear & general example with explanation, is below that provides some more clarity:
- Some more examples here:
- You can copy-paste the below code to following link, and modify it for your specific use case:
.
let m1 = new Map(); m1.set(6,1); // key 6 is number and type is preserved (can be strings too) m1.set(10,1); m1.set(100,1); m1.set(1,1); console.log(m1); // "string" sorted (even if keys are numbers) - default behaviour let m2 = new Map( [...m1].sort() ); // ...is destructuring into individual elements // then [] will catch elements in an array // then sort() sorts the array // since Map can take array as parameter to its constructor, a new Map is created console.log('m2', m2); // number sorted let m3 = new Map([...m1].sort((a, b) => { if (a[0] > b[0]) return 1; if (a[0] == b[0]) return 0; if (a[0] < b[0]) return -1; })); console.log('m3', m3); // Output // Map { 6 => 1, 10 => 1, 100 => 1, 1 => 1 } // m2 Map { 1 => 1, 10 => 1, 100 => 1, 6 => 1 } // Note: 1,10,100,6 sorted as strings, default. // Note: if the keys were string the sort behavior will be same as this // m3 Map { 1 => 1, 6 => 1, 10 => 1, 100 => 1 } // Note: 1,6,10,100 sorted as number, looks correct for number keys Hope that helps.
I would suggest to use a custom iterator for your map object to achieve a sorted access, like so:
map[Symbol.iterator] = function* () { yield* [...map.entries()].sort((a, b) => a[0].localeCompare(b[0])); } Using an iterator has the advantage of that it has only to be declared once. After adding/deleting entries in the map, a new for-loop over the map would automatically reflect this changes using an iterator. Sorted copies as shown in most of the above answers would not as they only reflect the map's state at exactly one point in time.
Here's the complete working example using your initial situation.
var map = new Map(); map.set('2-1', { name: 'foo' }); map.set('0-1', { name: 'bar' }); for (let [key, val] of map) { console.log(key + ' - ' + val.name); } // 2-1 - foo // 1-0 - bar map[Symbol.iterator] = function* () { yield* [...map.entries()].sort((a, b) => a[0].localeCompare(b[0])); } for (let [key, val] of map) { console.log(key + ' - ' + val.name); } // 1-0 - bar // 2-1 - foo map.set('2-0', { name: 'zzz' }); for (let [key, val] of map) { console.log(key + ' - ' + val.name); } // 1-0 - bar // 2-0 - zzz // 2-1 - foo Regards.
Perhaps a more realistic example about not sorting a Map object but preparing the sorting up front before doing the Map. The syntax gets actually pretty compact if you do it like this. You can apply the sorting before the map function like this, with a sort function before map (Example from a React app I am working on using JSX syntax)
Mark that I here define a sorting function inside using an arrow function that returns -1 if it is smaller and 0 otherwise sorted on a property of the Javascript objects in the array I get from an API.
report.ProcedureCodes.sort((a, b) => a.NumericalOrder < b.NumericalOrder ? -1 : 0).map((item, i) => <TableRow key={i}> <TableCell>{item.Code}</TableCell> <TableCell>{item.Text}</TableCell> {/* <TableCell>{item.NumericalOrder}</TableCell> */} </TableRow> ) As far as I see it's currently not possible to sort a Map properly.
The other solutions where the Map is converted into an array and sorted this way have the following bug:
var a = new Map([[1, 2], [3,4]]) console.log(a); // a = Map(2) {1 => 2, 3 => 4} var b = a; console.log(b); // b = Map(2) {1 => 2, 3 => 4} a = new Map(); // this is when the sorting happens console.log(a, b); // a = Map(0) {} b = Map(2) {1 => 2, 3 => 4} The sorting creates a new object and all other pointers to the unsorted object get broken.
Slight variation - I didn't have spread syntax and I wanted to work on an object instead of a Map.
Object.fromEntries(Object.entries(apis).sort()) Additionally to the correct answer above:
If the sorted Map is just needed in an angular template it can be easily sorted using the KeyValuePipe which has a default sort included:
<div *ngFor="let item of map | keyvalue"> {{ item.key }}: {{ item.value }} </div>