37

I have some JSON data that I get from a server. In my JavaScript, I want to do some sorting on it. I think the sort() function will do what I want.

However, it seems that JavaScript is converting the JSON data into an Object immediately on arrival. If I try to use the sort() method, I get errors a-plenty (using Firebug for testing).

I've looked around the net, and everyone seems to say that for one thing, JSON objects are already JavaScript arrays, and also that Objects can be treated just like arrays. Like over on this question, where in one of the answers, a guy says "The [Object object] is your data -- you can access it as you would an array."

However, that is not exactly true. JavaScript won't let me use sort() on my object. And since the default assumption is that they're all the same thing, there don't seem to be any instructions anywhere on how to convert an Object to an Array, or force JavaScript to treat it as one, or anything like that.

So... how do I get JavaScript to let me treat this data as an array and sort() it?

Console log output of my object looks like this (I want to be able to sort by the values in the "level"):

OBJECT JSONdata

{ 
1: {
    displayName: "Dude1",
    email: "dude1@example.com<mailto:dude1@example.com>",
    lastActive: 1296980700, 
    level: 57, 
    timeout: 12969932837
}, 2: {
    displayName: "Dude2",
    email: "dude2@example.com<mailto:dude2@example.com>",
    lastActive: 1296983456,
    level: 28,
    timeout: 12969937382
}, 3: {
    displayName: "Dude3",
    email: "dude3@example.com<mailto:dude3@example.com>",
    lastActive: 1296980749,
    level: 99,
    timeout: 129699323459
} 
}
Community
  • 1
  • 1
Questioner
  • 6,379
  • 15
  • 51
  • 87
  • Why don't you use a JSON array if you want an `Array`? `["foo","bar","baz"]` is an `Array`, `{0:"foo",1:"bar",2:"baz"}` is an `Object`... Not all `Object`s with numeric keys are `Array`s for one the `Array` has a `length` and a bunch of methods defined that an `Object` doesn't – tobyodavies Feb 08 '11 at 07:03

7 Answers7

42

Array.prototype.slice.call(arrayLikeObject)

is the standard way to convert and an array-like object to an array.

That only really works for the arguments object. To convert a generic object to an array is a bit of a pain. Here's the source from underscore.js:

_.toArray = function(iterable) {
    if (!iterable)                return [];
    if (iterable.toArray)         return iterable.toArray();
    if (_.isArray(iterable))      return iterable;
    if (_.isArguments(iterable))  return slice.call(iterable);
    return _.values(iterable);
};

_.values = function(obj) {
    return _.map(obj, _.identity);
};

Turns out you're going to need to loop over your object and map it to an array yourself.

var newArray = []
for (var key in object) {
    newArray.push(key);
}

You're confusing the concepts of arrays and "associative arrays". In JavaScript, objects kind of act like an associative array since you can access data in the format object["key"]. They're not real associative arrays since objects are unordered lists.

Objects and arrays are vastly different.

An example of using underscore:

var sortedObject = _.sortBy(object, function(val, key, object) {
    // return an number to index it by. then it is sorted from smallest to largest number
    return val;
});

See live example

Ry-
  • 199,309
  • 51
  • 404
  • 420
Raynos
  • 156,883
  • 55
  • 337
  • 385
  • Can I sort an associative array in Javascript? Assuming the values in the keys are sortable (the key names are text, but the values are numbers.) – Questioner Feb 08 '11 at 05:45
  • @user184108 nope. There is no native sort for this. Take a look at underscore.js which implements functionality like [`_.sortBy`](http://documentcloud.github.com/underscore/#sortBy) Either use underscore or write a small library to do this for you. – Raynos Feb 08 '11 at 05:48
  • Uh... this whole thing went way over my noob head. I've downloaded the underscore code... so I can use underscore to convert my object to an array, and then sort on that array...? – Questioner Feb 08 '11 at 05:56
  • @user184108 it's ok see the edit. Play with the jsfiddle and take a look at the underscore.js documentation. – Raynos Feb 08 '11 at 06:02
  • The _.sortyBy function did the trick! Works great. Used it to copy my object into a new array, and after that, the array sort() function worked as advertised. It doesn't seem to need the "key" and "object" parameters to be specified, though if I took them out entirely that broke the code. So I left them in as per the example. Thanks everyone for the help!! – Questioner Feb 08 '11 at 07:07
  • `for ... in`, for an object in javascript is a code smell. `hasOwnProperty` should be used to prevent adding elements from the object's prototype. – Alan May 27 '13 at 00:34
  • Just an aside, it seems that if you add a `length` property to an object with enumerated properties, then the `Array.prototype.slice.call(arrayLikeObject)` technique *does* in-fact seem to work. – Peter Jan 13 '14 at 10:08
7

You should be able to convert a JavaScript object into an array like so...

var obj = {
    '1': 'a',
    '2': 'b',
    '3': 'c'  
};

var arr = [];

for (var key in obj) {
    if (obj.hasOwnProperty(key)) {
      arr.push(obj[key]);  
    }
}

console.log(arr); // ["a", "b", "c"]

See it on jsFiddle.

alex
  • 438,662
  • 188
  • 837
  • 957
  • Okay, did that, but if I echo out the results of array into the console, it seems to be empty. It's just two square brackets. [ ] – Questioner Feb 08 '11 at 05:42
  • @user184108 Can you post the output of `console.log()` for your object? – alex Feb 08 '11 at 05:44
  • @alex try passing in an object {"1": 1}. Array.prototype.slice.call only works on the arguments object – Raynos Feb 08 '11 at 05:45
  • @Raynos I see. I guess the next thing would be to explicitly loop and add them one at a time? – alex Feb 08 '11 at 05:48
  • @alex that's what underscore.js does I doubt there's a better way. – Raynos Feb 08 '11 at 05:49
  • @Raynos My brain tells me there is a better way, but can not recall it. – alex Feb 08 '11 at 05:51
  • @alex checked both jQuery source and prototype source and they use loops. If you know a better way go make pull requests on the jQuery & prototype source. – Raynos Feb 08 '11 at 05:56
  • @Raynos If they use loops - then I'm pretty confident it would be the only way (or fastest by a significant factor). – alex Feb 08 '11 at 06:00
  • I put a version of the console output for my object in the original question, because the comments won't allow line breaks, and the object becomes unreadable when it's all on the same line. – Questioner Feb 08 '11 at 06:00
6

If your JavaScript object is an array-like object, that is, an Object instance with a valid numerical length property, then you can directly use many native Array methods on it thanks to the call method. For example:

// Sorts the given objet in-place as if it was an array
Array.prototype.sort.call(yourObject);

So if you know the number of entries to be sorted (How to efficiently count the number of keys/properties of an object in JavaScript?), you can do:

yourObject.length = theNumberOfEntries;
Array.prototype.sort.call(yourObject);
// Optionally: delete yourObject.length;

Note that this will only sort properties indexed by "0", "1", "2", ... to length - 1 inclusive, like in an Array. The object's other properties will not be re-ordered.

Community
  • 1
  • 1
Luc125
  • 5,365
  • 29
  • 34
  • Very interesting but I don't understand what you mean by indexed properties. Could you show an example ? thanx – charlysisto Mar 21 '13 at 13:46
  • @charlysisto What I mean here by 'property index' is just its key, used to access its value. When the context suggests that we are dealing with numerical keys I like to call them 'property indices' and 'property names' if I know they are textual. In ´var obj = { "length": 3, "1": value1, "2": value2, "3": value3 }` we have four properties whose indices are respectively "length" "1" "2" and "3". – Luc125 Mar 22 '13 at 02:39
5

Most of these answers over-complicate the issue or use JQuery or Underscore whereas the OP never asked for those.

You can convert an object to an array like this:

myArray= Object.keys(data).map(function(key) { return data[key] });

And sort the result like this:

myArray.sort(function(x, y) {return x.level - y.level});

If you need the id/index, then you need to do a bit more:

Object.keys(data).map(function(key) { 
  var obj = data[key];
  obj.index = key;
  return obj 
});
andyhasit
  • 10,953
  • 5
  • 39
  • 44
  • I presume the order of the array returned by `map` is deterministic? – Petrus Theron Oct 10 '16 at 15:10
  • map will always yield in the same order as the array provided, so it is deterministic, but in this case that comes from Object.keys() which doesn't guarantee any order, so overall this solution is not deterministic, and none of the other answers will be either, unless you add your own sorting. – andyhasit Oct 11 '16 at 20:28
2

I have stumbled upon that problem recently while trying to group an array of objects by one of it's properties, resulting in one object I could therefore not sort.

Concretely it's an array of blog posts I wanted to group by year and have them sorted by descending years. I used underscore's utility :

var grouped = _.groupBy(blogposts, function(post){
  var date = new Date(post.publication_date)
  return date.getFullYear()
})
//=> { 2010: [blogpost, blogpost,etc], 2011: [blogpost, etc] }

As @Raynos explained I had to end up with some sort of array first before sorting it...

It turns out underscore (1.4) has a nice little utility called pairs which will map the {key: value} of your object in an array of [key, value]. The equivalent of :

var paired = _.map(grouped, function(val, key){
  return [key, val]
})
//=> [ [2010, [blogpost, blogpost, ...] ], [2011, [blogpost, blogpost, ...]]...]

From there on you can easily sort by the first term of each pair.

Here's the end result:

var grouped = _.groupBy(result.resource, function(resource){
  var date = new Date(resource.pub_date)
  return date.getFullYear() //+ "." + (date.getMonth()+1)
})

var paired = _.pairs(grouped)

var sorted = _.sortBy(paired, function(pairs){
  return -parseInt(pairs[0])
})

return sorted;
// Giving me the expected result:
//=> [ [2013, [blogpost, blogpost, ...] ], [2012, [blogpost, blogpost, ...]]...]

I'm sure though there's a better and more performant way, but coming from ruby this code is immediately understandable for me.

charlysisto
  • 3,710
  • 15
  • 27
1

jQuery offers a map function, which will iterate through each element in an array or object and map the results into a new array.

Prior to jQuery 1.6, $.map() supported traversing arrays only.

We can use this to convert any object to an array as follows...

  myArray = $.map(myObject, function (el) {
    return el;
  });

But... if the callback function returns null or undefined, then that value is removed from the array, in most cases this is useful, but it can cause problems if you need null values in myArray.

jQuery offers a solution for this... return the value as an array with the single value

myArrayWithNulls = jQuery.map(myObject, function (el) {
  return [el];
});

Here's a fiddle demonstrating the two approaches: http://jsfiddle.net/chim/nFyPE/

http://jsperf.com/object-to-array-jquery-2

chim
  • 7,655
  • 3
  • 44
  • 58
1

I wrote a small function to recursively convert an object with properties that may also be objects to a multi-dimensional array. This code is dependent on underscore or lodash for the forEach and toArray methods.

function deepToArray(obj) {
    var copy = [];


    var i = 0;
    if (obj.constructor == Object ||
        obj.constructor == Array) {

        _.forEach(obj, process);

    } else {

        copy = obj;

    }


    function process(current, index, collection) {

        var processed = null;
        if (current.constructor != Object &&
            current.constructor != Array) {
            processed = current;
        } else {
            processed = deepToArray(_.toArray(current));
        }

        copy.push(processed);

    }

    return copy;
}

Here is the fiddle: http://jsfiddle.net/gGT2D/

Note: This was written to convert an object that was originally an array back into an array, so any non-array index key values will be lost.

Isioma Nnodum
  • 1,268
  • 12
  • 13