values - sort object javascript




How to sort an array of objects with multiple field values in JavaScript (8)

I found a great method to sort an array of objects based on one of the properties as defined at:

Sort array of objects by string property value in JavaScript

Using that function works perfectly for a single sort (on all browsers), and even a sort within another sort EXCEPT using Google Chrome! Here is Ege Özcan's great sort routine for arrays of objects

function dynamicSort(property) { 
    return function (a,b) {
        return (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;
    }
}

Using an array named "Data" (of course, my array has many more object pairs)...

var Data = [{Category: "Business", Value: "ABC"},{Category:"Personal", Value:"XYZ"}];

I can get a proper sort where the order is listed as all the values within each category by doing this...

Data.sort(dynamicSort("Value"));
Data.sort(dynamicSort("Category"));

By first sorting on Value, and then by Category, my array puts all values in sorted order with all the Business-base values listed first and then all the Personal-based values. Perfect! Except in Chrome where the data is sorted properly by category, but the order of the values within each category seems rather random.

Does any one know of a better way to do a sort within a sort that would also work in Chrome?


underscore.js

use underscore, its small and awesome...

sortBy_.sortBy(list, iterator, [context]) Returns a sorted copy of list, ranked in ascending order by the results of running each value through iterator. Iterator may also be the string name of the property to sort by (eg. length).

var objs = [ 
  { first_nom: 'Lazslo',last_nom: 'Jamf' },
  { first_nom: 'Pig', last_nom: 'Bodine'  },
  { first_nom: 'Pirate', last_nom: 'Prentice' }
];

var sortedObjs = _.sortBy( objs, 'first_nom' );

I created a multi-parameter version of that dynamicSort function:

function dynamicSort(property) { 
    return function (obj1,obj2) {
        return obj1[property] > obj2[property] ? 1
            : obj1[property] < obj2[property] ? -1 : 0;
    }
}

function dynamicSortMultiple() {
    /*
     * save the arguments object as it will be overwritten
     * note that arguments object is an array-like object
     * consisting of the names of the properties to sort by
     */
    var props = arguments;
    return function (obj1, obj2) {
        var i = 0, result = 0, numberOfProperties = props.length;
        /* try getting a different result from 0 (equal)
         * as long as we have extra properties to compare
         */
        while(result === 0 && i < numberOfProperties) {
            result = dynamicSort(props[i])(obj1, obj2);
            i++;
        }
        return result;
    }
}

I created an array as follows:

var arr = [
    {a:"a",b:"a",c:"a"},
    {a:"b",b:"a",c:"b"},
    {a:"b",b:"a",c:"a"},
    {a:"b",b:"a",c:"b"},
    {a:"b",b:"b",c:"a"},
    {a:"b",b:"b",c:"b"},
    {a:"b",b:"b",c:"a"},
    {a:"b",b:"b",c:"b"},
    {a:"b",b:"b",c:"a"},
    {a:"b",b:"b",c:"b"},
    {a:"b",b:"b",c:"a"},
    {a:"c",b:"b",c:"b"},
    {a:"c",b:"c",c:"a"}
];

and it worked when I did,

arr.sort(dynamicSortMultiple("c","b","a"));

And here is a working example: http://jsfiddle.net/ZXedp/


The easiest way to perform a Javascript Multi-Criteria Sort (or Multi-Parameter Sort), is to use .sort, concatenate the multiple parameters together, and compare the two stings.

For example:

data.sort(function (a, b) {

  var aConcat = a["property1"] + a["property2"];
  var bConcat = b["property1"] + b["property2"];

  if (aConcat > bConcat) {
    return 1;
  } else if (aConcat < bConcat) {
    return -1;
  } else {
    return 0;
  }

});

I've included a JsFiddle Script here: http://jsfiddle.net/oahxg4u3/6/


You can also create a dynamic sort function that sorts objects by their value that you pass:

function dynamicSort(property) {
    var sortOrder = 1;
    if(property[0] === "-") {
        sortOrder = -1;
        property = property.substr(1);
    }
    return function (a,b) {
        var result = (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;
        return result * sortOrder;
    }
}

So you can have an array of objects like this:

var People = [
    {Name: "Name", Surname: "Surname"},
    {Name:"AAA", Surname:"ZZZ"},
    {Name: "Name", Surname: "AAA"}
];

...and it will work when you do:

People.sort(dynamicSort("Name"));
People.sort(dynamicSort("Surname"));
People.sort(dynamicSort("-Surname"));

Actually this already answers the question. Below part is written because many people contacted me, complaining that it doesn't work with multiple parameters.

Multiple Parameters

You can use the function below to generate sort functions with multiple sort parameters.

function dynamicSortMultiple() {
    /*
     * save the arguments object as it will be overwritten
     * note that arguments object is an array-like object
     * consisting of the names of the properties to sort by
     */
    var props = arguments;
    return function (obj1, obj2) {
        var i = 0, result = 0, numberOfProperties = props.length;
        /* try getting a different result from 0 (equal)
         * as long as we have extra properties to compare
         */
        while(result === 0 && i < numberOfProperties) {
            result = dynamicSort(props[i])(obj1, obj2);
            i++;
        }
        return result;
    }
}

Which would enable you to do something like this:

People.sort(dynamicSortMultiple("Name", "-Surname"));

Adding It To The Prototype

(Implementation which is just below is inspired from Mike R's answer)

I wouldn't recommend changing a native object prototype but just to give an example so you can implement it on your own objects (For the environments that support it, you can also use Object.defineProperty as shown in the next section, which at least doesn't have the negative side-effect of being enumerable, as described at the last part)

Prototype implementation would be something like the following (Here's a working example):

//Don't just copy-paste this code. You will break the "for-in" loops
!function() {
    function _dynamicSortMultiple(attr) {
       /* dynamicSortMultiple function body comes here */
    }
    function _dynamicSort(property) {
        /* dynamicSort function body comes here */
    }
    Array.prototype.sortBy = function() {
        return this.sort(_dynamicSortMultiple.apply(null, arguments));
    }
}();

The "OK" Way Of Adding It To The Prototype

If you're targeting IE v9.0 and up then, as I previously mentioned, use Object.defineProperty like this (working example):

//Won't work below IE9, but totally safe otherwise
!function() {
    function _dynamicSortMultiple(attr) {
       /* dynamicSortMultiple function body comes here */
    }
    function _dynamicSort(property) {
        /* dynamicSort function body comes here */
    }
    Object.defineProperty(Array.prototype, "sortBy", {
        enumerable: false,
        writable: true,
        value: function() {
            return this.sort(_dynamicSortMultiple.apply(null, arguments));
        }
    });
}();

This can be an acceptable compromise until the bind operator arrives.

All those prototype fun enables this:

People.sortBy("Name", "-Surname");

You Should Read This

If you use the direct prototype access method (Object.defineProperty is fine) and other code does not check hasOwnProperty, kittens die! Ok, to be honest, no harm comes to any kitten really but probably things will break and every other developer in your team will hate you:

See that last "SortBy"? Yeah. Not cool. Use Object.defineProperty where you can, and leave the Array.prototype alone otherwise.


var arr = [{
    name: "GREG",
    hours: "40"
}, {
    name: "ANDY",
    hours: "50"
}, {
    name: "ANDY",
    hours: "40"
}];

Array.prototype.sortOn = function(conds){
    this.sort(function(a, b){
        for(var i=0; i < conds.length; i++){
            var c = conds[i].split(" ");
            if(a[c[0]] < b[c[0]]){
                return c[1] == "asc" ? -1:1;
            }
            if(a[c[0]] > b[c[0]]){
                return c[1] == "asc" ? 1:-1;
            }
        }
        return 0;
    });
    return this;
}

arr.sortOn(["name asc", "hours dsc"]);

obj.sort(function(item1,item2) {
  if ( item1.Name < item2.Name )
    return -1;
  if ( item1.Name > item2.Name )
    return 1;
  return item1.Hours - item2.Hours;
});

Grouped sorting on a JS array

array = [
    {name: "ANDY", hours: 40 }, 
    {name: "GREG", hours: 40 },
    {name: "ANDY", hours: 50 }, 
]

function cmp(x, y) {
  return x > y ? 1 : (x < y ? -1 : 0);
}


array.sort(function(a, b) {
  return cmp(a.name, b.name) || cmp(b.hours, a.hours)
})

console.log(array)

If javascript had a spaceship operator that would be even more elegant. Note that this code is easy to extend to use more properties:

ary.sort(function(a, b) {
    return cmp(a.name, b.name) || cmp(a.age, b.age) || cmp(b.hours, a.hours) || ....
})

JavaScript: group items by a key, sort items within(!) the groups by another key - how?

The sort function needed to be expanded to cover when the rooms are equal. When the rooms are equal we then compare the age.

students.sort(function(a, b) {
    if(a.room_id < b.room_id){
        return -1;  
    }else if(a.room_id > b.room_id){
        return 1;
    }else{
        if(a.age < b.age){
           return -1
        }else if(a.age > b.age){
          return 1;
        }else{
          return 0;
        }
    }
});

Working Example http://jsfiddle.net/n9KNx/1/