Guilherme B
Guilherme B

Reputation: 75

How to sort an array where the keys are numeric strings

I need to create one Object where the keys are strings composed of numbers but I couldn't sort them in the correct way:

let simple_arr = ['00', '11', '22', '33'];
let simple_obj = {};

for (let it of simple_arr) {
  simple_obj[it] = "anything";
}

for (let it2 in simple_obj) { 
  console.log(it2)
}

The output that I would like is: 00, 11, 22, 33.

But it reproduces: 11, 22, 33, 00.

I already tried to sort this object with Object.keys, but always when I set an iterator on it the value "00" goes to the end.

Upvotes: 0

Views: 72

Answers (3)

T.J. Crowder
T.J. Crowder

Reputation: 1074365

You cannot rely on the output you've said you want using a for-in loop. Although object properties have order as of ES2015, for-in loops don't. (Neither does the array from Object.keys.)

If you want order, you want an array. (But you can use a Map, too, in some situations; see below.)

Moreover, even with ES2015's property order, the keys you've shown will never be in the order you've shown, because '00' is different from '11', '22', and '33': It's not a canonical number String. The ES2015 order (for the operations that support it) is properties whose names are strings in canonical number String format, in order numerically, followed by other string-named properties in the order they were added, followed by Symbol-named properties.

So your object will never behave as you've said you want. Use an array.

(Of course, if you introduce a prefix so none of them is in canonical number String format and add the properties in the order you want them, that would work with Object.getOwnPropertyNames and other operations that support the order. But I really wouldn't do that.)

Another option is to use a Map, which unlike objects always follows the order that the keys were first used.¹ If you create a Map like this:

const map = new Map(simple_arr.map(key => [key, "anything"]));

...then looping through that map will reliably visit the '00' entry, then the '11' entry, then the '22' entry, then the '33' entry:

const simple_arr = ['00', '11', '22', '33'];
const map = new Map(simple_arr.map(key => [key, "anything"]));
for (const [key, value] of map.entries()) {
    console.log(key, value);
}


¹ That's the simple version. The longer version: If you add a key/value to the map and that key is not already in the map, it goes at the end of the list of the map's keys. If you set a new value for that key, it doesn't move in that list. If you remove the key and then add it again later, it goes at the end again.

Upvotes: 4

Dan
Dan

Reputation: 10538

Objects have no concept of ordering when it comes to their keys. Even if you could achieve this with this particular data set in your particular browser, there's no guarantee the keys will come out in the order you want.

If you really want to achieve this, you should sort the keys returned from Object.keys() and iterate over that.


My original answer above is incorrect. As of ES2015, object keys do have a specific order. I still maintain that trying to rely on the order of keys of an object is probably a bad idea - Use a Map to retain the original values of your keys (without them being coerced to a string) and order the keys when iterating, instead.

Upvotes: 3

Ori Drori
Ori Drori

Reputation: 191976

Since ES2015 object keys do have a traversal order:

  • Strings that are legitimate numbers (Integer indices) come first, and ordered by their value
  • Strings that aren't are placed after numeric strings, and their order is defined by the order of entry

Convert 00 to 0 so it will be ordered by the numeric value:

const simple_arr = ['0', '11', '22', '33'];
const simple_obj = {};
for(let it of simple_arr){
  simple_obj[it] = "anything";
}

for(let it2 in simple_obj){ 
  console.log(it2)
}

Upvotes: 1

Related Questions