115

For example the following

var data = {
    'States': ['NSW', 'VIC'],
    'Countries': ['GBR', 'AUS'],
    'Capitals': ['SYD', 'MEL']
}
for (var item in data) {
    console.log(item);
}

prints

States
Countries
Capitals

Is there a way to sort alphabetically so that it prints

Capitals
Countries
States
Hoa
  • 19,858
  • 28
  • 78
  • 107

9 Answers9

164

Not within the object itself: the property collection of an object is unordered.

One thing you could do is use Object.keys(), and sort the Array, then iterate it.

Object.keys(data)
      .sort()
      .forEach(function(v, i) {
          console.log(v, data[v]);
       });

Patches (implementations) for browsers that do not support ECMAScript 5th edition:

  • 1
    It's worth adding that .sort() can be replaced with .replace() if you want the sort order reversed. Also, sort() and reverse() accept compare functions as arguments, allowing you to fine-tune the sorting. http://www.w3schools.com/jsref/jsref_sort.asp – Juniper Jones May 13 '16 at 18:46
  • 7
    @CharlesJaimet: Not sure what you mean by `.replace()`. Did you mean `.reverse()`? That can indeed be used, but only if you want the current order reversed. If the sorted order is to be reversed, it must first be sorted. The `.sort()` method does indeed accept a function, but `.reverse()` does not. –  May 13 '16 at 19:35
  • This solved my problem, but I'd love to know what the `i` argument in the function(v, i) represents – William Carron Dec 20 '16 at 22:41
  • 2
    @WillCarron: It's the numeric indices of the current iteration. So if there were 3 keys, the `i` would be given `0`, `1`, `2` on each respective invocation of the callback. Because `i` isn't used in the function, it can be safely removed, since JS never requires that you provide parameters for all or any of the arguments passed. Using the logging facilities available in the browser, you can examine the value of the data in your application. `console.log("The value of 'i' is:", i)` –  Dec 20 '16 at 22:47
  • This answer is out of date. The properties of an object are ordered (integer-string keys ascending, other string keys in creation order, symbol keys in creation order). – Ben Aston Oct 24 '22 at 22:33
57

here's a nice functional solution:

basically,

  1. extract the keys into a list with Object.keys
  2. sort the keys
  3. reduce list back down to an object to get desired result

ES5 Solution:

not_sorted = {b: false, a: true};

sorted = Object.keys(not_sorted)
    .sort()
    .reduce(function (acc, key) { 
        acc[key] = not_sorted[key];
        return acc;
    }, {});

console.log(sorted) //{a: true, b: false}

ES6 Solution:

not_sorted = {b: false, a: true}

sorted = Object.keys(not_sorted)
    .sort()
    .reduce((acc, key) => ({
        ...acc, [key]: not_sorted[key]
    }), {})

console.log(sorted) //{a: true, b: false}
mac
  • 579
  • 4
  • 4
21

Yes, there is. Not within ECMAScript standard, but supported across browsers and Node.js, and apparently stable. See https://stackoverflow.com/a/23202095/645715.

EDIT: This returns an object in which the keys are ordered. You can use Object.keys(...) to get the ordered keys from the object.

Why worry about object key order? The difference can matter in some applications, such as parsing XML with xml2js which represents XML as nested objects, and uses XML tags as hash keys.

There are a couple of notes:

  • keys that look like integers appear first and in numeric order.
  • keys that look like strings appear next and in insertion order.
  • this order is reported by Object.keys(obj)
  • the order as reported by for (var key in obj) {...} may differ in Safari, Firefox

The function returns an object with sorted keys inserted in alphabetic order:

function orderKeys(obj) {

  var keys = Object.keys(obj).sort(function keyOrder(k1, k2) {
      if (k1 < k2) return -1;
      else if (k1 > k2) return +1;
      else return 0;
  });

  var i, after = {};
  for (i = 0; i < keys.length; i++) {
    after[keys[i]] = obj[keys[i]];
    delete obj[keys[i]];
  }

  for (i = 0; i < keys.length; i++) {
    obj[keys[i]] = after[keys[i]];
  }
  return obj;
}

Here's a quick test:

var example = { 
      "3": "charlie",
      "p:style": "c",
      "berries": "e",
      "p:nvSpPr": "a",
      "p:txBody": "d",
      "apples": "e",
      "5": "eagle",
      "p:spPr": "b"
    }

var obj = orderKeys(example);

this returns

{ '3': 'charlie',
  '5': 'eagle',
  apples: 'e',
  berries: 'e',
  'p:nvSpPr': 'a',
  'p:spPr': 'b',
  'p:style': 'c',
  'p:txBody': 'd' }

You can then get the ordered keys as:

Object.keys(obj) 

Which returns

["3", "5", "apples", "berries", "p:nvSpPr", "p:spPr", "p:style", "p:txBody"]
Timo
  • 2,922
  • 3
  • 29
  • 28
prototype
  • 7,249
  • 15
  • 60
  • 94
  • yes thank you. works great on something like this: {2018-04-02 05:46:51: "0.0000003900", 2018-04-02 04:46:48: "0.0000004000", 2018-04-03 01:00:35: "0.0000004000", 2018-04-02 06:46:55: "0.0000003900", 2018-04-02 02:46:34: "0.0000004000", …}2018-04-02 01:46:25: "0.0000003900"2018-04-02 02:46:34: "0.0000004000"2018-04-02 03:46:42: "0.0000003900"2018-04-02 04:46:48: "0.0000004000"2018-04-02 05:46:51: "0.0000003900"2018-04-02 06:46:55: "0.0000003900"2018-04-03 00:00:32: "0.0000003900"2018-04-03 01:00:35: "0.0000004000"__proto__: Object – Ray Koren Apr 03 '18 at 01:23
  • Thanks a lot for this. What does `expected` do here? It doesn't complain when not supplied. – Vael Victus Dec 30 '19 at 13:56
  • 2
    @VaelVictus Great catch, it was copy-pasted from a unit test and not necessary now. – prototype Jan 06 '20 at 05:15
  • 1
    @VaelVictus I removed `expected` as parameter. I think JS is flexible here so that is why it did not complain when calling it without the second arg. This is sorting `keys for dummies`, I wonder how this in typescript would look like. – Timo Oct 01 '22 at 07:08
19

You can also sort it in this way.

const data = {
  States: ['NSW', 'VIC'],
  Countries: ['GBR', 'AUS'],
  Capitals: ['SYD', 'MEL']
}

const sortedObject = Object.fromEntries(Object.entries(data).sort())

I cannot explain in detail why it works but apparently it's working flawlessly :) Try yourself if you're not believing me :D

Note that your browser or Node.js version should support Object.fromEntries

For browser compatibility check bottom of this page

For Node, it will work for versions from 12 and higher. For Deno from the v1.

Andrii Los
  • 486
  • 5
  • 8
6

I would add this as a comment to @prototype's post, but my rep isn't high enough.

If anyone needs a version of orderKeys that @prototype wrote that is compliant with eslint-config-airbnb:

/**
 * Returns and modifies the input object so that its keys are returned in sorted
 * order when `Object.keys(obj)` is invoked
 *
 * @param {object} obj The object to have its keys sorted
 *
 * @returns {object} The inputted object with its keys in sorted order
 */
const orderKeys = (obj) => {
  // Complying with `no-param-reassign`, and JavaScript seems to assign by reference here
  const newObj = obj;
  // Default `.sort()` chained method seems to work for me
  const keys = Object.keys(newObj).sort();

  const after = {};
  // Add keys to `after` in sorted order of `obj`'s keys
  keys.forEach((key) => {
    after[key] = newObj[key];
    delete newObj[key];
  });

  // Add keys back to `obj` in sorted order
  keys.forEach((key) => {
    newObj[key] = after[key];
  });

  return newObj;
};

Using @prototype's tests:

const example = { 
  3: 'charlie',
  'p:style': 'c',
  berries: 'e',
  'p:nvSpPr': 'a',
  'p:txBody': 'd',
  apples: 'e',
  5: 'eagle',
  p:spPr: 'b'
}

const obj = orderKeys(example);

console.log(obj);

console.log(Object.keys(obj));

Outputs the following:

Babel Compiler v6.4.4
Copyright (c) 2014-2015 Sebastian McKenzie

{ 3: 'charlie',
  5: 'eagle',
  apples: 'e',
  berries: 'e',
  'p:nvSpPr': 'a',
  'p:spPr': 'b',
  'p:style': 'c',
  'p:txBody': 'd' }
[ '3',
  '5',
  'apples',
  'berries',
  'p:nvSpPr',
  'p:spPr',
  'p:style',
  'p:txBody' ]

For whatever it's worth, I needed this in my React app so that I could sort the options of a dropdown that was based on a state object, assigned after a response from my API.

Initially I did

return (
  // ...
  <Select
    options={Object.keys(obj).sort()}
    // ...
  />
  // ...
);

But realized the .sort() method would be invoked on each re-render, hence needing @prototype's implementation of orderKeys.

https://stackoverflow.com/users/645715/prototype

CorreyL
  • 118
  • 1
  • 9
  • I can confirm that this solution also works with many levels of deeply nested objects, but it only sorts the top level keys, which was my use case. Thanks! – Yuniac Mar 05 '23 at 16:17
3

Here's a one-liner to sort an object's keys using lodash

_.chain(obj).toPairs().sortBy(0).fromPairs().value()

With your example data:

var data = {
    'States': ['NSW', 'VIC'],
    'Countries': ['GBR', 'AUS'],
    'Capitals': ['SYD', 'MEL']
}
data = _.chain(data)
  .toPairs() // turn the object into an array of [key, value] pairs
  .sortBy(0) // sort these pairs by index [0] which is [key]
  .fromPairs() // convert array of pairs back into an object {key: value}
  .value() // return value
/*
{
  Capitals: [ 'SYD', 'MEL' ],
  Countries: [ 'GBR', 'AUS' ],
  States: [ 'NSW', 'VIC' ]
}
*/

Jinksi
  • 71
  • 4
2

Here is a nice solution if you have a more complex object where some of the properties are also objects.

const sortObject = (obj: any) => {
  const sorted = Object.keys(obj)
    .sort()
    .reduce((accumulator, key) => {
      if (typeof obj[key] === "object") {
        // recurse nested properties that are also objects
        if (obj[key] == null) {
          accumulator[key] = null;
        } else if (isArray(obj[key])) {
          accumulator[key] = obj[key].map((item: any) => {
            if (typeof item === "object") {
              return sortObject(item);
            } else {
              return item;
            }
          });
        } else {
          accumulator[key] = sortObject(obj[key]);
        }
      } else {
        accumulator[key] = obj[key];
      }
      return accumulator;
    }, {});
  return sorted;
};
Schof
  • 123
  • 2
  • 3
  • You should present a full example with data, so that readers know what you mean with `complex`. – Timo Oct 01 '22 at 15:19
1

Improved this answer to ES6, shortened to one loop and with typescript

function orderKeys(obj: {}) {
  var keys = (Object.keys(obj) as Array<keyof typeof obj>).sort((k1, k2) => {
    if (k1 < k2) return -1;
    else if (k1 > k2) return +1;
    else return 0;
  });

  var helpArr = {};
  for (var elem of keys) {
    helpArr[elem] = obj[elem];
    delete obj[elem];
    obj[elem] = helpArr[elem]
  }
  return obj;
}


var data = {
    'States': ['NSW', 'VIC'],
    'Countries': ['GBR', 'AUS'],
    'Capitals': ['SYD', 'MEL']
}

Or with ES6 and reduce

const sorted = (Object.keys(data) as Array<keyof typeof data>).sort().reduce((r: any, k) => ({ ...r, [k]: data[k] }), {});

Result ordered by key

console.log(orderKeys(data)) # similar: console.log(sorted) 
{
Capitals: [ 'SYD', 'MEL' ],
Countries: [ 'GBR', 'AUS' ],
States: [ 'NSW', 'VIC' ]
}
Timo
  • 2,922
  • 3
  • 29
  • 28
-3

If conversion to an array does not suit your template and you know the keys of your object you can also do something like this:

In your controller define an array with the keys in the correct order:

this.displayOrder = [
    'firstKey',
    'secondKey',
    'thirdKey'
];

In your template repeat the keys of your displayOrder and then use ng-init to reference back to your object.

<div ng-repeat="key in ctrl.displayOrder" ng-init="entry = ctrl.object[key]">
     {{ entry.detail }}
</div>
Stanley85
  • 389
  • 1
  • 5
  • 7