30

I noticed that certain code that evaluates some shoe sizes for an e-commerce site and outputs them on screen is messing up the order in Chrome.

JSON given can be:

{
  "7": ["9149", "9139", "10455", "17208"],
  "7.5": ["9140", "9150", "10456", "17209"],
  "8": ["2684", "9141", "10457", "17210"],
  "8.5": ["9142", "10444", "10458", "17211"],
  "9": ["2685", "9143", "10459", "17212"],
  "9.5": ["10443", "9144", "10460", "17213"]
}

...which increments sizes in halves.

Upon conversion into an object and iterating though the keys, the natural order is being respected and they come out as:

7, 7.5, 8, 8.5 etc.

But in Chrome only, keys that 'look' like round numbers ALWAYS come out of the object first, so output of a for... in loop is:

7, 8, 9, 7.5, 8.5, 9.5 ...

Object.keys(sizes); // ["7", "8", "9", "7.5", "8.5", "9.5"]

Here is the test case: https://jsfiddle.net/wcapc46L/1/

It's only affects whole numbers, seems like Webkit / Blink have an optimisation that prefers Object properties that are numeric, maybe it's to do with Branch Prediction or whatever.

If you prefix the object keys with any character, the order remains unaffected and works as intended - FIFO

I think I recall reading that there are no guarantees on the order of properties of an object but at the same time, this is annoying to the extreme and would cause a considerable amount of effort in fixing it for chrome users alone.

Any ideas? is this likely a bug that will get fixed?

edit additionally, I have now discovered this as an issue on the v8 bug tracker:

https://code.google.com/p/v8/issues/detail?id=164

Looks like Blink don't want to fix this and will remain the only browser that will do it.

update whatever hash table optimisation webkit/blink had, has now made its way into gecko (FF 27.0.1) - https://jsfiddle.net/9Htmq/ results in 7,8,9,7.5,8.5,9.5. applying _ before the keys returns the correct / expected order.

update 2017 People are still upvoting and editing this so - It does NOT appear to affect Map / WeakMap, Set etc (as demonstrated by updated main example)

Syscall
  • 19,327
  • 10
  • 37
  • 52
Dimitar Christoff
  • 26,147
  • 8
  • 50
  • 69
  • 1
    at the time of development, chrome was nowhere near and it now amounts to 7% of site users. until chrome came along, it worked as intended in all browsers, safari included--so it not a webkit issue. and yes, i do understand that it may need refactoring but was being hopeful of an easier solution (or that chrome will actually be fixing this like everyone else). for now, I may have to prefix all object properties with __ or something to force natural order of definition. – Dimitar Christoff Jul 06 '10 at 15:19
  • I disagree with the word "fix": Chrome has nothing to fix here. Use an `Array`. – Tim Down Jul 06 '10 at 15:27
  • 1
    the reason why i am not using an array is because the array key that is integer will affect the array length. `var sizes = []; sizes['8'] = "bar";` -> results in null values 0-7 and length change. basically, I was looking for associative array functionality which is available through key => value pairs via `object`. the only fix that works at present is to prefix all keys with a _ and thus avoid numerics - which I just did on the live page. – Dimitar Christoff Jul 06 '10 at 15:52
  • I'm suggesting your JSON could look more like `[{"size":7,"data":["9149","9139","10455","17208"]},{"size":7.5,"data":["9140","9150","10456","17209"]}]`. – Tim Down Jul 06 '10 at 16:21
  • 1
    this makes my cross-object mapping a lot more difficult. sizes is one of 3 associative objects. size (key sizes, array of product version ids that have it), alongside of colour (key colour name along with version ids that support it) and versions (key id, array of many other properties, including colour and size). the interchange takes place on the associative key lookup so having to loop through all sizes to get a match for .size == 7 will be difficult and costly (considering real time stock data via comet/ajax). anyway -thanks a lot, much appreciated for all the useful comments and ideas! +1 – Dimitar Christoff Jul 06 '10 at 16:30

7 Answers7

24

It's the way v8 handles associative arrays. A known issue Issue 164 but it follows the spec so is marked 'working as intended'. There isn't a required order for looping through associative arrays.

A simple workaround is to precede number values with letters e.g: 'size_7':['9149','9139'] etc.

The standard will change in the next ECMAScript spec forcing [chrome] developers to change this.

Michael Sparks
  • 645
  • 3
  • 10
  • 1
    change in favour of chrome's current implementation or in favour of preserving the order of definition? – Dimitar Christoff Jul 06 '10 at 14:19
  • 2
    preserving the order since all other browsers already do this. – Michael Sparks Jul 06 '10 at 14:27
  • 1
    *"The standard will change in the next ECMAScript spec forcing developers to change this."* Really? Where's the evidence for this? The recent ECMAScript 5 spec doesn't specify an order. Sounds like it got dropped at the last minute; who's to say that won't happen again next time? – Tim Down Jul 06 '10 at 14:44
  • perhaps implied for harmony and not ECMA 5 – Dimitar Christoff Jul 06 '10 at 14:56
  • 1
    It might not be part of the spec. I read about it in John Resig's blog . http://ejohn.org/blog/javascript-in-chrome/ – Michael Sparks Jul 06 '10 at 15:02
  • hrm he does state `All modern implementations of ECMAScript iterate through object properties in the order in which they were defined. Because of this the Chrome team has deemed this to be a bug and will be fixing it.` - i read that as a positive sign the--again it got posted in 2008 and it's still not the case. – Dimitar Christoff Jul 06 '10 at 15:07
  • 4
    Well, silly Mr Resig, if he relied on this. Relying on non-uniformly observed rather than specified behaviour is a recipe for bad, unreliable code. – Tim Down Jul 06 '10 at 15:26
  • Chakra and Carakan (in IE9+ and Opera 10.50+) also have this behaviour, not just V8. – gsnedders May 07 '13 at 23:03
  • I tried turning the index to a string instead of a number, but Chrome seems to rearrange the order anyway. @Michael Sparks – Reality-Torrent Dec 01 '15 at 10:38
  • @Reality-Torrent Simply converting to a string isn't enough here. You'll need a character before the key. Here is an example. http://jsfiddle.net/9Htmq/5/ A good way to think about it is, "What will this return when passed to `parseInt`?". If the answer is `NaN` it should work. – Michael Sparks Dec 03 '15 at 16:16
  • @MichaelSparks With ES2020, I think this answer is now incorrect. – Ben Aston Mar 11 '20 at 17:02
1

It would appear that Chrome is treating the integer string as if it were a numeric type when used as an index/property name.

I think relying on the Javascript implementation to preserve the order of what, in some cases, is object properties, and in other cases (certainly with chrome) array indices, is demonstrably an unsafe approach and order of enumeration is probably not defined in the spec. I would suggest adding an additional property to the JSON that indicates a sort order:

{
    "7":{"sortOrder":1,"data":["9149","9139","10455","17208"]},
    "7.5":{"sortOrder":2,"data":["9140","9150","10456","17209"]}
    //etc
}
spender
  • 117,338
  • 33
  • 229
  • 351
1

They're being treated as strings because they are strings. My best suggestion would be to use the same "precision" in all your keys.

{
  "7.0": ["9149", "9139", "10455", "17208"],
  "7.5": ["9140", "9150", "10456", "17209"],
  "8.0": ["2684", "9141", "10457", "17210"],
  "8.5": ["9142", "10444", "10458", "17211"],
  "9.0": ["2685", "9143", "10459", "17212"],
  "9.5": ["10443", "9144", "10460", "17213"]
}

So, "8.0" instead of "8", etc.

Even then, there are no guarantees, but it is more likely that they'll come out in the same order.

For a better guarantee, perform a sort based on the keys, putting the values into an array in the sorted order.

Mickael Lherminez
  • 679
  • 1
  • 10
  • 29
Ryan Kinal
  • 17,414
  • 6
  • 46
  • 63
  • regretfully, even though this will work for the test case, this won't be an option for the implementation. sizes are a multitude of various options, such as `S,M,L,XL,XXL' or '44,44.5,44.5' or 'One Size' and so forth, as defined on a per-product basis by the merchant. there is tremendous logic involved in sorting the output from MYSQL so it arrives in the correct order and not as strings - it covers something like 40 separate sizes pre-defined in order of preference. – Dimitar Christoff Jul 06 '10 at 14:21
1

When iterating over the properties of an object, the order is specified in the ECMAScript specification as being undefined and any order you may have observed in some environment should not be relied upon. If you need order, use an Array.

Tim Down
  • 318,141
  • 75
  • 454
  • 536
0

I found an easy work around using underscore.js

myArray = _.sortBy(myArray, function(num){ return Math.ceil(num); });

Yay! myArray is back to the correct order in all browsers.

rink.attendant.6
  • 44,500
  • 61
  • 101
  • 156
Greg
  • 1,007
  • 1
  • 9
  • 9
0

I don't think you can call this a bug. Like you say yourself, there is no garantee on how the properties of an object are sorted.

Ridcully
  • 23,362
  • 7
  • 71
  • 86
0

My keys were too important for me to convert them to strings. Instead I created another array which just maintained the order of the keys.

<?php 
$origArray = valueReturnedFromFunction();
$preservedOrder = array_keys($origArray);
?>
<script>
var origArray = <?php echo json_encode($origArray)?>;
var preservedOrder = <?php echo json_encode($preservedOrder )?>;

for(i in preservedOrder){
    var item = origArray[i];
    ...
}
</script>