19

I need for some debugging to see the original order of one JavaScript object's properties but (at least in chrome devtools) console.log() shows me an alphabetically ordered object.

Ex:

var obj = {
    z: 1,
    t: 2,
    y: 3,
    a: 4,
    n: 5,
    k: 6
}

console.log(obj) shows this:

Object {z: 1, t: 2, y: 3, a: 4, n: 5…}
a:4
k:6
n:5
t:2
y:3
z:1

//expected (needed ) original order
z: 1
t: 2
y: 3
a: 4
n: 5
k: 6
MTK
  • 3,300
  • 2
  • 33
  • 49
  • 4
    Do you really believe that javascript object properties have an order? – Gerardo Furtado Aug 20 '16 at 13:39
  • 4
    Objects are unordered collections. There's never a *guarantee* that you'll get any particular order. Sounds like your approach to debugging may need to be tweaked. –  Aug 20 '16 at 13:39
  • 1
    You have to use an array, because object don't guarantee that properties will be the same order every time you iterate over them. Arrays do guarantee that order. – Swimburger Aug 20 '16 at 13:41
  • 3
    These comments are so outdated... – Klesun Dec 15 '20 at 14:09
  • 1
    Yes these comments are outdated - but it's helpful to say why! Here's a good answer to confirm that they do in fact have an order https://stackoverflow.com/a/31102605/16940. However watch out because the Chrome devtools DOES sort properties in the inspector which may not be the order they were added / will be iterated in. This can greatly add to the confusion until you realize what's going on! (or if you forget about it) – Simon_Weaver Jul 14 '21 at 08:05

6 Answers6

29

console.log does indeed sort the properties, in some cases you can use JSON.stringify which preserves the order, e.g.

console.log(JSON.stringify(obj, null /*replacer function */, 4 /* space */))

NB: contrary to the popular belief, js objects maintain the enumeration order, as per the OwnPropertyKeys specification (integers first, then other properties in insertion order)

Taranjeet Singh
  • 159
  • 1
  • 10
georg
  • 211,518
  • 52
  • 313
  • 390
9

Objects do retain the order that their (non-numeric) keys were inserted in, but they are only guaranteed to iterate in that order using certain methods. Per the specification, Object.keys and its variants, JSON.stringify, and for..in loops all iterate in an unspecified order. These methods all call EnumerateObjectProperties, which explicitly states:

The mechanics and order of enumerating the properties is not specified

While environments generally iterate in the predictable order anyway for those methods, that behavior is in no way guaranteed by the specification.

But, Object.getOwnPropertyNames (and Reflect.ownKeys, and Object.getOwnPropertySymbols) are guaranteed to iterate in a particular order: ascending numeric keys, followed by other keys in insertion order, per [[OwnPropertyKeys]].

So, a specification-guaranteed method of logging (non-numeric) properties in insertion order will involve using one of the above methods, rather than Object.keys or its variants:

var obj = {
  z: 1,
  t: 2,
  y: 3,
  a: 4,
  n: 5,
  k: 6
};

const str = '{\n' +
  Object.getOwnPropertyNames(obj).map(key => `  ${key}: ${obj[key]}`).join('\n')
  + '\n}';
console.log(str);

For nested objects, you'll need a recursive function instead:

var obj = {
  z: 1,
  t: 2,
  y: 3,
  a: 4,
  nested: {
    foo: 9,
    bar: 99,
    baz: 35
  },
  n: 5,
  k: 6
};

const objToArrOfLines = (obj, lines=[], leftPadding=0, keyForThisObj) => {
  lines.push(`${' '.repeat(leftPadding)}${keyForThisObj ? keyForThisObj + ': ' : ''}{`);
  Object.getOwnPropertyNames(obj).forEach((key) => {
    const val = obj[key];
    if (typeof val === 'object') {
      objToArrOfLines(val, lines, leftPadding + 2, key);
    } else {
      lines.push(`${' '.repeat(leftPadding + 2)}${key}: ${val}`);
    }
  });
  lines.push(`${' '.repeat(leftPadding)}}`);
  return lines;
};
const objToStr = (obj) => {
  console.log(objToArrOfLines(obj).join('\n'));
};

objToStr(obj);
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
2

Another easy solution would be:

console.log(Object.entries(obj).map(k=>({[k[0]]:k[1]})))
Observablerxjs
  • 692
  • 6
  • 22
2

Just to clarify Console.log does sort but it also doesn't....

This is a list of breakpoints, the correct order (as it was created) is default, mobileM, mobileL, tablet, desktopM and that's what's show in the first line.

enter image description here

But when you expand it they're in alphabetical order. Same if you hover over something and view the popup.

The point being one minute it does sort them and the next it doesn't. Really ought to be an option, but doesn't seem to be - and this mixed behavior can really trip you up if you're normally not concerned about property order.

Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689
2

The output of console.table(yourObj) outputs in the order you'd expect it to, though as a table.

Caveat: Objects keys that are numerical (only numbers, even if wrapped within quotes) are ALWAYS sorted from least to most in modern browsers, however, as soon as a non-numerical character is met in the key it ignores that key from being sorted. There are hackish workarounds that take advantage of this fact like prepending all keys with _ or some other non-numerical character to prevent the objects from being auto-sorted. See https://www.stefanjudis.com/today-i-learned/property-order-is-predictable-in-javascript-objects-since-es2015/ for more

Kerry Johnson
  • 842
  • 9
  • 16
1

If you need to log a very big object, to be able to collapse keys, another option would be to transform it to key-value pair arrays.

let keepKeyOrder = function(obj) {
    if (typeof obj === 'object' && !Array.isArray(obj)) {
        let transformKey = (k) => [k, keepKeyOrder(obj[k])];
        return Object.keys(obj).map(transformKey);
    } else {
        return obj;
    }
};

console.log(keepKeyOrder({a:3,c:4,b:{b3:123,b2:234,b1:345}}));

Outputs:

enter image description here

Klesun
  • 12,280
  • 5
  • 59
  • 52