[javascript] How to sort an array of objects by multiple fields?

From this original question, how would I apply a sort on multiple fields?

Using this slightly adapted structure, how would I sort city (ascending) & then price (descending)?

var homes = [
    {"h_id":"3",
     "city":"Dallas",
     "state":"TX",
     "zip":"75201",
     "price":"162500"},
    {"h_id":"4",
     "city":"Bevery Hills",
     "state":"CA",
     "zip":"90210",
     "price":"319250"},
    {"h_id":"6",
     "city":"Dallas",
     "state":"TX",
     "zip":"75000",
     "price":"556699"},
    {"h_id":"5",
     "city":"New York",
     "state":"NY",
     "zip":"00010",
     "price":"962500"}
    ];

I liked the fact than an answer was given which provided a general approach. Where I plan to use this code, I will have to sort dates as well as other things. The ability to "prime" the object seemed handy, if not a little cumbersome.

I've tried to build this answer into a nice generic example, but I'm not having much luck.

This question is related to javascript arrays sorting

The answer is


Here is a simple functional approach. Specify sort order using array. Prepend minus to specify descending order.

var homes = [
    {"h_id":"3", "city":"Dallas", "state":"TX","zip":"75201","price":"162500"},
    {"h_id":"4","city":"Bevery Hills", "state":"CA", "zip":"90210", "price":"319250"},
    {"h_id":"6", "city":"Dallas", "state":"TX", "zip":"75000", "price":"556699"},
    {"h_id":"5", "city":"New York", "state":"NY", "zip":"00010", "price":"962500"}
    ];

homes.sort(fieldSorter(['city', '-price']));
// homes.sort(fieldSorter(['zip', '-state', 'price'])); // alternative

function fieldSorter(fields) {
    return function (a, b) {
        return fields
            .map(function (o) {
                var dir = 1;
                if (o[0] === '-') {
                   dir = -1;
                   o=o.substring(1);
                }
                if (a[o] > b[o]) return dir;
                if (a[o] < b[o]) return -(dir);
                return 0;
            })
            .reduce(function firstNonZeroValue (p,n) {
                return p ? p : n;
            }, 0);
    };
}

Edit: in ES6 it's even shorter!

_x000D_
_x000D_
"use strict";_x000D_
const fieldSorter = (fields) => (a, b) => fields.map(o => {_x000D_
    let dir = 1;_x000D_
    if (o[0] === '-') { dir = -1; o=o.substring(1); }_x000D_
    return a[o] > b[o] ? dir : a[o] < b[o] ? -(dir) : 0;_x000D_
}).reduce((p, n) => p ? p : n, 0);_x000D_
_x000D_
const homes = [{"h_id":"3", "city":"Dallas", "state":"TX","zip":"75201","price":162500},     {"h_id":"4","city":"Bevery Hills", "state":"CA", "zip":"90210", "price":319250},{"h_id":"6", "city":"Dallas", "state":"TX", "zip":"75000", "price":556699},{"h_id":"5", "city":"New York", "state":"NY", "zip":"00010", "price":962500}];_x000D_
const sortedHomes = homes.sort(fieldSorter(['state', '-price']));_x000D_
_x000D_
document.write('<pre>' + JSON.stringify(sortedHomes, null, '\t') + '</pre>')
_x000D_
_x000D_
_x000D_


Adaptation of @chriskelly 's answer.


Most answers overlook that price will not sort properly if the value is in the ten thousands and lower or over a million. The resaon being JS sorts alphabetically. It was answered pretty well here, Why can't JavaScript sort "5, 10, 1" and here How to sort an array of integers correctly.

Ultimately we have to do some evaluation if the field or node we're sorting by is an number. I am not saying that using parseInt() in this case is the correct answer, the sorted results are more important.

_x000D_
_x000D_
var homes = [{_x000D_
  "h_id": "2",_x000D_
  "city": "Dallas",_x000D_
  "state": "TX",_x000D_
  "zip": "75201",_x000D_
  "price": "62500"_x000D_
}, {_x000D_
  "h_id": "1",_x000D_
  "city": "Dallas",_x000D_
  "state": "TX",_x000D_
  "zip": "75201",_x000D_
  "price": "62510"_x000D_
}, {_x000D_
  "h_id": "3",_x000D_
  "city": "Dallas",_x000D_
  "state": "TX",_x000D_
  "zip": "75201",_x000D_
  "price": "162500"_x000D_
}, {_x000D_
  "h_id": "4",_x000D_
  "city": "Bevery Hills",_x000D_
  "state": "CA",_x000D_
  "zip": "90210",_x000D_
  "price": "319250"_x000D_
}, {_x000D_
  "h_id": "6",_x000D_
  "city": "Dallas",_x000D_
  "state": "TX",_x000D_
  "zip": "75000",_x000D_
  "price": "556699"_x000D_
}, {_x000D_
  "h_id": "5",_x000D_
  "city": "New York",_x000D_
  "state": "NY",_x000D_
  "zip": "00010",_x000D_
  "price": "962500"_x000D_
}];_x000D_
_x000D_
homes.sort(fieldSorter(['price']));_x000D_
// homes.sort(fieldSorter(['zip', '-state', 'price'])); // alternative_x000D_
_x000D_
function fieldSorter(fields) {_x000D_
  return function(a, b) {_x000D_
    return fields_x000D_
      .map(function(o) {_x000D_
        var dir = 1;_x000D_
        if (o[0] === '-') {_x000D_
          dir = -1;_x000D_
          o = o.substring(1);_x000D_
        }_x000D_
        if (!parseInt(a[o]) && !parseInt(b[o])) {_x000D_
          if (a[o] > b[o]) return dir;_x000D_
          if (a[o] < b[o]) return -(dir);_x000D_
          return 0;_x000D_
        } else {_x000D_
          return dir > 0 ? a[o] - b[o] : b[o] - a[o];_x000D_
        }_x000D_
      })_x000D_
      .reduce(function firstNonZeroValue(p, n) {_x000D_
        return p ? p : n;_x000D_
      }, 0);_x000D_
  };_x000D_
}_x000D_
document.getElementById("output").innerHTML = '<pre>' + JSON.stringify(homes, null, '\t') + '</pre>';
_x000D_
<div id="output">_x000D_
_x000D_
</div>
_x000D_
_x000D_
_x000D_


A fiddle to test with


function sortMultiFields(prop){
    return function(a,b){
        for(i=0;i<prop.length;i++)
        {
            var reg = /^\d+$/;
            var x=1;
            var field1=prop[i];
            if(prop[i].indexOf("-")==0)
            {
                field1=prop[i].substr(1,prop[i].length);
                x=-x;
            }

            if(reg.test(a[field1]))
            {
                a[field1]=parseFloat(a[field1]);
                b[field1]=parseFloat(b[field1]);
            }
            if( a[field1] > b[field1])
                return x;
            else if(a[field1] < b[field1])
                return -x;
        }
    }
}

How to use (put -(minus) sign before field if you want to sort in descending order particular field)

homes.sort(sortMultiFields(["city","-price"]));

Using above function you can sort any json array with multiple fields. No need to change function body at all


A multi dimensional sorting method, based on this answer:

Update: Here is an "optimized" version. It does a lot more preprocessing and creates a comparison function for each sorting option beforehand. It might need more more memory (as it stores a function for each sorting option, but it should preform a bit better as it does not have to determine the correct settings during the comparison. I have not done any profiling though.

var sort_by;

(function() {
    // utility functions
    var default_cmp = function(a, b) {
            if (a == b) return 0;
            return a < b ? -1 : 1;
        },
        getCmpFunc = function(primer, reverse) {
            var dfc = default_cmp, // closer in scope
                cmp = default_cmp;
            if (primer) {
                cmp = function(a, b) {
                    return dfc(primer(a), primer(b));
                };
            }
            if (reverse) {
                return function(a, b) {
                    return -1 * cmp(a, b);
                };
            }
            return cmp;
        };

    // actual implementation
    sort_by = function() {
        var fields = [],
            n_fields = arguments.length,
            field, name, reverse, cmp;

        // preprocess sorting options
        for (var i = 0; i < n_fields; i++) {
            field = arguments[i];
            if (typeof field === 'string') {
                name = field;
                cmp = default_cmp;
            }
            else {
                name = field.name;
                cmp = getCmpFunc(field.primer, field.reverse);
            }
            fields.push({
                name: name,
                cmp: cmp
            });
        }

        // final comparison function
        return function(A, B) {
            var a, b, name, result;
            for (var i = 0; i < n_fields; i++) {
                result = 0;
                field = fields[i];
                name = field.name;

                result = field.cmp(A[name], B[name]);
                if (result !== 0) break;
            }
            return result;
        }
    }
}());

Example usage:

homes.sort(sort_by('city', {name:'price', primer: parseInt, reverse: true}));

DEMO


Original function:

var sort_by = function() {
   var fields = [].slice.call(arguments),
       n_fields = fields.length;

   return function(A,B) {
       var a, b, field, key, primer, reverse, result, i;

       for(i = 0; i < n_fields; i++) {
           result = 0;
           field = fields[i];

           key = typeof field === 'string' ? field : field.name;

           a = A[key];
           b = B[key];

           if (typeof field.primer  !== 'undefined'){
               a = field.primer(a);
               b = field.primer(b);
           }

           reverse = (field.reverse) ? -1 : 1;

           if (a<b) result = reverse * -1;
           if (a>b) result = reverse * 1;
           if(result !== 0) break;
       }
       return result;
   }
};

DEMO


Another way

_x000D_
_x000D_
var homes = [_x000D_
    {"h_id":"3",_x000D_
     "city":"Dallas",_x000D_
     "state":"TX",_x000D_
     "zip":"75201",_x000D_
     "price":"162500"},_x000D_
    {"h_id":"4",_x000D_
     "city":"Bevery Hills",_x000D_
     "state":"CA",_x000D_
     "zip":"90210",_x000D_
     "price":"319250"},_x000D_
    {"h_id":"6",_x000D_
     "city":"Dallas",_x000D_
     "state":"TX",_x000D_
     "zip":"75000",_x000D_
     "price":"556699"},_x000D_
    {"h_id":"5",_x000D_
     "city":"New York",_x000D_
     "state":"NY",_x000D_
     "zip":"00010",_x000D_
     "price":"962500"}_x000D_
    ];_x000D_
function sortBy(ar) {_x000D_
  return ar.sort((a, b) => a.city === b.city ?_x000D_
      b.price.toString().localeCompare(a.price) :_x000D_
      a.city.toString().localeCompare(b.city));_x000D_
}_x000D_
console.log(sortBy(homes));
_x000D_
_x000D_
_x000D_


function sort(data, orderBy) {
        orderBy = Array.isArray(orderBy) ? orderBy : [orderBy];
        return data.sort((a, b) => {
            for (let i = 0, size = orderBy.length; i < size; i++) {
                const key = Object.keys(orderBy[i])[0],
                    o = orderBy[i][key],
                    valueA = a[key],
                    valueB = b[key];
                if (!(valueA || valueB)) {
                    console.error("the objects from the data passed does not have the key '" + key + "' passed on sort!");
                    return [];
                }
                if (+valueA === +valueA) {
                    return o.toLowerCase() === 'desc' ? valueB - valueA : valueA - valueB;
                } else {
                    if (valueA.localeCompare(valueB) > 0) {
                        return o.toLowerCase() === 'desc' ? -1 : 1;
                    } else if (valueA.localeCompare(valueB) < 0) {
                        return o.toLowerCase() === 'desc' ? 1 : -1;
                    }
                }
            }
        });
    }

Using :

sort(homes, [{city : 'asc'}, {price: 'desc'}])

_x000D_
_x000D_
var homes = [_x000D_
    {"h_id":"3",_x000D_
     "city":"Dallas",_x000D_
     "state":"TX",_x000D_
     "zip":"75201",_x000D_
     "price":"162500"},_x000D_
    {"h_id":"4",_x000D_
     "city":"Bevery Hills",_x000D_
     "state":"CA",_x000D_
     "zip":"90210",_x000D_
     "price":"319250"},_x000D_
    {"h_id":"6",_x000D_
     "city":"Dallas",_x000D_
     "state":"TX",_x000D_
     "zip":"75000",_x000D_
     "price":"556699"},_x000D_
    {"h_id":"5",_x000D_
     "city":"New York",_x000D_
     "state":"NY",_x000D_
     "zip":"00010",_x000D_
     "price":"962500"}_x000D_
    ];_x000D_
function sort(data, orderBy) {_x000D_
            orderBy = Array.isArray(orderBy) ? orderBy : [orderBy];_x000D_
            return data.sort((a, b) => {_x000D_
                for (let i = 0, size = orderBy.length; i < size; i++) {_x000D_
                    const key = Object.keys(orderBy[i])[0],_x000D_
                        o = orderBy[i][key],_x000D_
                        valueA = a[key],_x000D_
                        valueB = b[key];_x000D_
                    if (!(valueA || valueB)) {_x000D_
                        console.error("the objects from the data passed does not have the key '" + key + "' passed on sort!");_x000D_
                        return [];_x000D_
                    }_x000D_
                    if (+valueA === +valueA) {_x000D_
                        return o.toLowerCase() === 'desc' ? valueB - valueA : valueA - valueB;_x000D_
                    } else {_x000D_
                        if (valueA.localeCompare(valueB) > 0) {_x000D_
                            return o.toLowerCase() === 'desc' ? -1 : 1;_x000D_
                        } else if (valueA.localeCompare(valueB) < 0) {_x000D_
                            return o.toLowerCase() === 'desc' ? 1 : -1;_x000D_
                        }_x000D_
                    }_x000D_
                }_x000D_
            });_x000D_
        }_x000D_
console.log(sort(homes, [{city : 'asc'}, {price: 'desc'}]));
_x000D_
_x000D_
_x000D_


Simpler one:

var someArray = [...];

function generateSortFn(props) {
    return function (a, b) {
        for (var i = 0; i < props.length; i++) {
            var prop = props[i];
            var name = prop.name;
            var reverse = prop.reverse;
            if (a[name] < b[name])
                return reverse ? 1 : -1;
            if (a[name] > b[name])
                return reverse ? -1 : 1;
        }
        return 0;
    };
};

someArray.sort(generateSortFn([{name: 'prop1', reverse: true}, {name: 'prop2'}]));

Wow, there are some complex solutions here. So complex I decided to come up with something simpler but also quite powerful. Here it is;

function sortByPriority(data, priorities) {
  if (priorities.length == 0) {
    return data;
  }

  const nextPriority = priorities[0];
  const remainingPriorities = priorities.slice(1);

  const matched = data.filter(item => item.hasOwnProperty(nextPriority));
  const remainingData = data.filter(item => !item.hasOwnProperty(nextPriority));

  return sortByPriority(matched, remainingPriorities)
    .sort((a, b) => (a[nextPriority] > b[nextPriority]) ? 1 : -1)
    .concat(sortByPriority(remainingData, remainingPriorities));
}

And here is an example of how you use it.

const data = [
  { id: 1,                         mediumPriority: 'bbb', lowestPriority: 'ggg' },
  { id: 2, highestPriority: 'bbb', mediumPriority: 'ccc', lowestPriority: 'ggg' },
  { id: 3,                         mediumPriority: 'aaa', lowestPriority: 'ggg' },
];

const priorities = [
  'highestPriority',
  'mediumPriority',
  'lowestPriority'
];


const sorted = sortByPriority(data, priorities);

This will first sort by the precedence of the attributes, then by the value of the attributes.


A dynamic way to do that with MULTIPLE keys:

  • filter unique values from each col/key of sort
  • put in order or reverse it
  • add weights width zeropad for each object based on indexOf(value) keys values
  • sort using caclutated weights

enter image description here

Object.defineProperty(Array.prototype, 'orderBy', {
value: function(sorts) { 
    sorts.map(sort => {            
        sort.uniques = Array.from(
            new Set(this.map(obj => obj[sort.key]))
        );

        sort.uniques = sort.uniques.sort((a, b) => {
            if (typeof a == 'string') {
                return sort.inverse ? b.localeCompare(a) : a.localeCompare(b);
            }
            else if (typeof a == 'number') {
                return sort.inverse ? (a < b) : (a > b ? 1 : 0);
            }
            else if (typeof a == 'boolean') {
                let x = sort.inverse ? (a === b) ? 0 : a? -1 : 1 : (a === b) ? 0 : a? 1 : -1;
                return x;
            }
            return 0;
        });
    });

    const weightOfObject = (obj) => {
        let weight = "";
        sorts.map(sort => {
            let zeropad = `${sort.uniques.length}`.length;
            weight += sort.uniques.indexOf(obj[sort.key]).toString().padStart(zeropad, '0');
        });
        //obj.weight = weight; // if you need to see weights
        return weight;
    }

    this.sort((a, b) => {
        return weightOfObject(a).localeCompare( weightOfObject(b) );
    });

    return this;
}
});

Use:

// works with string, number and boolean
let sortered = your_array.orderBy([
    {key: "type", inverse: false}, 
    {key: "title", inverse: false},
    {key: "spot", inverse: false},
    {key: "internal", inverse: true}
]);

enter image description here


Here 'AffiliateDueDate' and 'Title' are columns, both are sorted in ascending order.

array.sort(function(a, b) {

               if (a.AffiliateDueDate > b.AffiliateDueDate ) return 1;
               else if (a.AffiliateDueDate < b.AffiliateDueDate ) return -1;
               else if (a.Title > b.Title ) return 1;
               else if (a.Title < b.Title ) return -1;
               else return 0;
             })

This is a complete cheat but I think that it adds value to this question because it's basically a canned library function that you can use out-of-the box.

If your code has access to lodash or a lodash compatible library like underscore then you can use the _.sortBy method. The snippet below is copied directly from the lodash documentation.

The commented results in the examples looks like they return arrays of arrays but that's just showing the order and not the actual results which are an array of objects.

var users = [
  { 'user': 'fred',   'age': 48 },
  { 'user': 'barney', 'age': 36 },
  { 'user': 'fred',   'age': 40 },
  { 'user': 'barney', 'age': 34 }
];

_.sortBy(users, [function(o) { return o.user; }]);
 // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]

_.sortBy(users, ['user', 'age']);
// => objects for [['barney', 34], ['barney', 36], ['fred', 40], ['fred', 48]]

I think this may be the easiest way to do it.

https://coderwall.com/p/ebqhca/javascript-sort-by-two-fields

It's really simple and I tried it with 3 different key value pairs and it worked great.

Here is a simple example, look at the link for more details

testSort(data) {
    return data.sort(
        a['nameOne'] > b['nameOne'] ? 1
        : b['nameOne'] > a['nameOne'] ? -1 : 0 ||
        a['date'] > b['date'] ||
        a['number'] - b['number']
    );
}

Just another option. Consider to use the following utility function:

/** Performs comparing of two items by specified properties
 * @param  {Array} props for sorting ['name'], ['value', 'city'], ['-date']
 * to set descending order on object property just add '-' at the begining of property
 */
export const compareBy = (...props) => (a, b) => {
  for (let i = 0; i < props.length; i++) {
    const ascValue = props[i].startsWith('-') ? -1 : 1;
    const prop = props[i].startsWith('-') ? props[i].substr(1) : props[i];
    if (a[prop] !== b[prop]) {
      return a[prop] > b[prop] ? ascValue : -ascValue;
    }
  }
  return 0;
};

Example of usage (in your case):

homes.sort(compareBy('city', '-price'));

It should be noted that this function can be even more generalized in order to be able to use nested properties like 'address.city' or 'style.size.width' etc.


The following function will allow you to sort an array of objects on one or multiple properties, either ascending (default) or descending on each property, and allow you to choose whether or not to perform case sensitive comparisons. By default, this function performs case insensitive sorts.

The first argument must be the array containing the objects. The subsequent argument(s) must be a comma separated list of strings that reference the different object properties to sort by. The last argument (which is optional) is a boolean to choose whether or not to perform case sensitive sorts - use true for case sensitive sorts.

The function will sort each property/key in ascending order by default. If you want a particular key to sort in descending order, then instead pass in an array in this format: ['property_name', true].

Here are some sample uses of the function followed by an explanation (where homes is an array containing the objects):

objSort(homes, 'city') --> sort by city (ascending, case in-sensitive)

objSort(homes, ['city', true]) --> sort by city (descending, case in-sensitive)

objSort(homes, 'city', true) --> sort by city then price (ascending, case sensitive)

objSort(homes, 'city', 'price') --> sort by city then price (both ascending, case in-sensitive)

objSort(homes, 'city', ['price', true]) --> sort by city (ascending) then price (descending), case in-sensitive)

And without further ado, here's the function:

function objSort() {
    var args = arguments,
        array = args[0],
        case_sensitive, keys_length, key, desc, a, b, i;

    if (typeof arguments[arguments.length - 1] === 'boolean') {
        case_sensitive = arguments[arguments.length - 1];
        keys_length = arguments.length - 1;
    } else {
        case_sensitive = false;
        keys_length = arguments.length;
    }

    return array.sort(function (obj1, obj2) {
        for (i = 1; i < keys_length; i++) {
            key = args[i];
            if (typeof key !== 'string') {
                desc = key[1];
                key = key[0];
                a = obj1[args[i][0]];
                b = obj2[args[i][0]];
            } else {
                desc = false;
                a = obj1[args[i]];
                b = obj2[args[i]];
            }

            if (case_sensitive === false && typeof a === 'string') {
                a = a.toLowerCase();
                b = b.toLowerCase();
            }

            if (! desc) {
                if (a < b) return -1;
                if (a > b) return 1;
            } else {
                if (a > b) return -1;
                if (a < b) return 1;
            }
        }
        return 0;
    });
} //end of objSort() function

And here's some sample data:

var homes = [{
    "h_id": "3",
    "city": "Dallas",
    "state": "TX",
    "zip": "75201",
    "price": 162500
}, {
    "h_id": "4",
    "city": "Bevery Hills",
    "state": "CA",
    "zip": "90210",
    "price": 1000000
}, {
    "h_id": "5",
    "city": "new york",
    "state": "NY",
    "zip": "00010",
    "price": 1000000
}, {
    "h_id": "6",
    "city": "Dallas",
    "state": "TX",
    "zip": "85000",
    "price": 300000
}, {
    "h_id": "7",
    "city": "New York",
    "state": "NY",
    "zip": "00020",
    "price": 345000
}];

I was looking for something similar and ended up with this:

First we have one or more sorting functions, always returning either 0, 1 or -1:

const sortByTitle = (a, b): number => 
  a.title === b.title ? 0 : a.title > b.title ? 1 : -1;

You can create more functions for each other property you want to sort on.

Then I have a function that combines these sorting functions into one:

const createSorter = (...sorters) => (a, b) =>
  sorters.reduce(
    (d, fn) => (d === 0 ? fn(a, b) : d),
    0
  );

This can be used to combine the above sorting functions in a readable way:

const sorter = createSorter(sortByTitle, sortByYear)

items.sort(sorter)

When a sorting function returns 0 the next sorting function will be called for further sorting.


I like SnowBurnt's approach but it needs a tweak to test for equivalence on city NOT a difference.

homes.sort(
   function(a,b){
      if (a.city==b.city){
         return (b.price-a.price);
      } else {
         return (a.city-b.city);
      }
   });

Here is mine for your reference, with example:

function msort(arr, ...compFns) {
  let fn = compFns[0];
  arr = [].concat(arr);
  let arr1 = [];
  while (arr.length > 0) {
    let arr2 = arr.splice(0, 1);
    for (let i = arr.length; i > 0;) {
      if (fn(arr2[0], arr[--i]) === 0) {
        arr2 = arr2.concat(arr.splice(i, 1));
      }
    }
    arr1.push(arr2);
  }

  arr1.sort(function (a, b) {
    return fn(a[0], b[0]);
  });

  compFns = compFns.slice(1);
  let res = [];
  arr1.map(a1 => {
    if (compFns.length > 0) a1 = msort(a1, ...compFns);
    a1.map(a2 => res.push(a2));
  });
  return res;
}

let tstArr = [{ id: 1, sex: 'o' }, { id: 2, sex: 'm' }, { id: 3, sex: 'm' }, { id: 4, sex: 'f' }, { id: 5, sex: 'm' }, { id: 6, sex: 'o' }, { id: 7, sex: 'f' }];

function tstFn1(a, b) {
  if (a.sex > b.sex) return 1;
  else if (a.sex < b.sex) return -1;
  return 0;
}

function tstFn2(a, b) {
  if (a.id > b.id) return -1;
  else if (a.id < b.id) return 1;
  return 0;
}

console.log(JSON.stringify(msort(tstArr, tstFn1, tstFn2)));
//output:
//[{"id":7,"sex":"f"},{"id":4,"sex":"f"},{"id":5,"sex":"m"},{"id":3,"sex":"m"},{"id":2,"sex":"m"},{"id":6,"sex":"o"},{"id":1,"sex":"o"}]

Here's my solution based on the Schwartzian transform idiom, hope you find it useful.

function sortByAttribute(array, ...attrs) {
  // generate an array of predicate-objects contains
  // property getter, and descending indicator
  let predicates = attrs.map(pred => {
    let descending = pred.charAt(0) === '-' ? -1 : 1;
    pred = pred.replace(/^-/, '');
    return {
      getter: o => o[pred],
      descend: descending
    };
  });
  // schwartzian transform idiom implementation. aka: "decorate-sort-undecorate"
  return array.map(item => {
    return {
      src: item,
      compareValues: predicates.map(predicate => predicate.getter(item))
    };
  })
  .sort((o1, o2) => {
    let i = -1, result = 0;
    while (++i < predicates.length) {
      if (o1.compareValues[i] < o2.compareValues[i]) result = -1;
      if (o1.compareValues[i] > o2.compareValues[i]) result = 1;
      if (result *= predicates[i].descend) break;
    }
    return result;
  })
  .map(item => item.src);
}

Here's an example how to use it:

let games = [
  { name: 'Pako',              rating: 4.21 },
  { name: 'Hill Climb Racing', rating: 3.88 },
  { name: 'Angry Birds Space', rating: 3.88 },
  { name: 'Badland',           rating: 4.33 }
];

// sort by one attribute
console.log(sortByAttribute(games, 'name'));
// sort by mupltiple attributes
console.log(sortByAttribute(games, '-rating', 'name'));

Simplest Way to sort array of object by multiple fields:

 let homes = [ {"h_id":"3",
   "city":"Dallas",
   "state":"TX",
   "zip":"75201",
   "price":"162500"},
  {"h_id":"4",
   "city":"Bevery Hills",
   "state":"CA",
   "zip":"90210",
   "price":"319250"},
  {"h_id":"6",
   "city":"Dallas",
   "state":"TX",
   "zip":"75000",
   "price":"556699"},
  {"h_id":"5",
   "city":"New York",
   "state":"NY",
   "zip":"00010",
   "price":"962500"}
  ];

homes.sort((a, b) => (a.city > b.city) ? 1 : -1);

Output: "Bevery Hills" "Dallas" "Dallas" "Dallas" "New York"


Sorting on two date fields and a numeric field example:

var generic_date =  new Date(2070, 1, 1);
checkDate = function(date) {
  return Date.parse(date) ? new Date(date): generic_date;
}

function sortData() {  
  data.sort(function(a,b){
    var deltaEnd = checkDate(b.end) - checkDate(a.end);
    if(deltaEnd) return deltaEnd;

    var deltaRank = a.rank - b.rank;
    if (deltaRank) return deltaRank;

    var deltaStart = checkDate(b.start) - checkDate(a.start);
    if(deltaStart) return deltaStart;

    return 0;
  });
}

http://jsfiddle.net/hcWgf/57/


Here's a generic multidimensional sort, allowing for reversing and/or mapping on each level.

Written in Typescript. For Javascript, check out this JSFiddle

The Code

type itemMap = (n: any) => any;

interface SortConfig<T> {
  key: keyof T;
  reverse?: boolean;
  map?: itemMap;
}

export function byObjectValues<T extends object>(keys: ((keyof T) | SortConfig<T>)[]): (a: T, b: T) => 0 | 1 | -1 {
  return function(a: T, b: T) {
    const firstKey: keyof T | SortConfig<T> = keys[0];
    const isSimple = typeof firstKey === 'string';
    const key: keyof T = isSimple ? (firstKey as keyof T) : (firstKey as SortConfig<T>).key;
    const reverse: boolean = isSimple ? false : !!(firstKey as SortConfig<T>).reverse;
    const map: itemMap | null = isSimple ? null : (firstKey as SortConfig<T>).map || null;

    const valA = map ? map(a[key]) : a[key];
    const valB = map ? map(b[key]) : b[key];
    if (valA === valB) {
      if (keys.length === 1) {
        return 0;
      }
      return byObjectValues<T>(keys.slice(1))(a, b);
    }
    if (reverse) {
      return valA > valB ? -1 : 1;
    }
    return valA > valB ? 1 : -1;
  };
}

Usage Examples

Sorting a people array by last name, then first name:

interface Person {
  firstName: string;
  lastName: string;
}

people.sort(byObjectValues<Person>(['lastName','firstName']));

Sort language codes by their name, not their language code (see map), then by descending version (see reverse).

interface Language {
  code: string;
  version: number;
}

// languageCodeToName(code) is defined elsewhere in code

languageCodes.sort(byObjectValues<Language>([
  {
    key: 'code',
    map(code:string) => languageCodeToName(code),
  },
  {
    key: 'version',
    reverse: true,
  }
]));

Here's an extensible way to sort by multiple fields.

homes.sort(function(left, right) {
    var city_order = left.city.localeCompare(right.city);
    var price_order = parseInt(left.price) - parseInt(right.price);
    return city_order || -price_order;
});

Notes

  • A function passed to array sort is expected to return negative, zero, positive to indicate less, equal, greater.
  • a.localeCompare(b) is universally supported for strings, and returns -1,0,1 if a<b,a==b,a>b.
  • Subtraction works on numeric fields, because a - b gives -,0,+ if a<b,a==b,a>b.
  • || in the last line gives city priority over price.
  • Negate to reverse order in any field, as in -price_order
  • Date comparison, var date_order = new Date(left.date) - new Date(right.date); works like numerics because date math turns into milliseconds since 1970.
  • Add fields into the or-chain, return city_order || -price_order || date_order;
  • Subtraction works on boolean. I suggest you make it explicit, var goodness_order = Boolean(left.is_good) - Boolean(right.is_good)

I made a quite generic multi feature sorter today. You can have a look at thenBy.js here: https://github.com/Teun/thenBy.js

It allows you to use the standard Array.sort, but with firstBy().thenBy().thenBy() style. It is way less code and complexity than the solutions posted above.


Here is a generic version of @Snowburnt's solution:

var sortarray = [{field:'city', direction:'asc'}, {field:'price', direction:'desc'}];
array.sort(function(a,b){
    for(var i=0; i<sortarray.length; i++){
        retval = a[sortarray[i].field] < b[sortarray[i].field] ? -1 : a[sortarray[i].field] > b[sortarray[i].field] ? 1 : 0;
        if (sortarray[i].direction == "desc") {
            retval = retval * -1;
        }
        if (retval !== 0) {
            return retval;
        }
    }
}


})

This is based on a sort routine I'm using. I didn't test this specific code so it may have errors but you get the idea. The idea is to sort based on the first field that indicates a difference and then stop and go to the next record. So, if you're sorting by three fields and the first field in the compare is enough to determine the sort order of the two records being sorted then return that sort result and go to the next record.

I tested it (actually with a little more complex sort logic) on 5000 records and it did it in the blink of an eye. If you're actually loading more than 1000 records to the client you should probably be using sever-side sorting and filtering.

This code isn't handling case-sensitivity but I leave it to the reader to handle this trivial modification.


for a non-generic, simple solution to your exact problem:

homes.sort(
   function(a, b) {          
      if (a.city === b.city) {
         // Price is only important when cities are the same
         return b.price - a.price;
      }
      return a.city > b.city ? 1 : -1;
   });

This is a recursive algorithm to sort by multiple fields while having the chance to format values before comparison.

var data = [
{
    "id": 1,
    "ship": null,
    "product": "Orange",
    "quantity": 7,
    "price": 92.08,
    "discount": 0
},
{
    "id": 2,
    "ship": "2017-06-14T23:00:00.000Z".toDate(),
    "product": "Apple",
    "quantity": 22,
    "price": 184.16,
    "discount": 0
},
...
]
var sorts = ["product", "quantity", "ship"]

// comp_val formats values and protects against comparing nulls/undefines
// type() just returns the variable constructor
// String.lower just converts the string to lowercase.
// String.toDate custom fn to convert strings to Date
function comp_val(value){
    if (value==null || value==undefined) return null
    var cls = type(value)
    switch (cls){
        case String:
            return value.lower()
    }
    return value
}

function compare(a, b, i){
    i = i || 0
    var prop = sorts[i]
    var va = comp_val(a[prop])
    var vb = comp_val(b[prop])

    // handle what to do when both or any values are null
    if (va == null || vb == null) return true

    if ((i < sorts.length-1) && (va == vb)) {
        return compare(a, b, i+1)
    } 
    return va > vb
}

var d = data.sort(compare);
console.log(d);

If a and b are equal it will just try the next field until none is available.


homes.sort(function(a,b) { return a.city - b.city } );
homes.sort(function(a,b){
    if (a.city==b.city){
        return parseFloat(b.price) - parseFloat(a.price);
    } else {
        return 0;
    }
});

How about this simple solution:

const sortCompareByCityPrice = (a, b) => {
    let comparison = 0
    // sort by first criteria
    if (a.city > b.city) {
        comparison = 1
    }
    else if (a.city < b.city) {
        comparison = -1
    }
    // If still 0 then sort by second criteria descending
    if (comparison === 0) {
        if (parseInt(a.price) > parseInt(b.price)) {
            comparison = -1
        }
        else if (parseInt(a.price) < parseInt(b.price)) {
            comparison = 1
        }
    }
    return comparison 
}

Based on this question javascript sort array by multiple (number) fields


Here's another one that's perhaps closer to your idea for the syntax

function sortObjects(objArray, properties /*, primers*/) {
    var primers = arguments[2] || {}; // primers are optional

    properties = properties.map(function(prop) {
        if( !(prop instanceof Array) ) {
            prop = [prop, 'asc']
        }
        if( prop[1].toLowerCase() == 'desc' ) {
            prop[1] = -1;
        } else {
            prop[1] = 1;
        }
        return prop;
    });

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

    function arrayCmp(a, b) {
        var arr1 = [], arr2 = [];
        properties.forEach(function(prop) {
            var aValue = a[prop[0]],
                bValue = b[prop[0]];
            if( typeof primers[prop[0]] != 'undefined' ) {
                aValue = primers[prop[0]](aValue);
                bValue = primers[prop[0]](bValue);
            }
            arr1.push( prop[1] * valueCmp(aValue, bValue) );
            arr2.push( prop[1] * valueCmp(bValue, aValue) );
        });
        return arr1 < arr2 ? -1 : 1;
    }

    objArray.sort(function(a, b) {
        return arrayCmp(a, b);
    });
}

// just for fun use this to reverse the city name when sorting
function demoPrimer(str) {
    return str.split('').reverse().join('');
}

// Example
sortObjects(homes, ['city', ['price', 'desc']], {city: demoPrimer});

Demo: http://jsfiddle.net/Nq4dk/2/


Edit: Just for fun, here's a variation that just takes an sql-like string, so you can do sortObjects(homes, "city, price desc")

function sortObjects(objArray, properties /*, primers*/) {
    var primers = arguments[2] || {};

    properties = properties.split(/\s*,\s*/).map(function(prop) {
        prop = prop.match(/^([^\s]+)(\s*desc)?/i);
        if( prop[2] && prop[2].toLowerCase() === 'desc' ) {
            return [prop[1] , -1];
        } else {
            return [prop[1] , 1];
        }
    });

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

    function arrayCmp(a, b) {
        var arr1 = [], arr2 = [];
        properties.forEach(function(prop) {
            var aValue = a[prop[0]],
                bValue = b[prop[0]];
            if( typeof primers[prop[0]] != 'undefined' ) {
                aValue = primers[prop[0]](aValue);
                bValue = primers[prop[0]](bValue);
            }
            arr1.push( prop[1] * valueCmp(aValue, bValue) );
            arr2.push( prop[1] * valueCmp(bValue, aValue) );
        });
        return arr1 < arr2 ? -1 : 1;
    }

    objArray.sort(function(a, b) {
        return arrayCmp(a, b);
    });
}

Examples related to javascript

need to add a class to an element How to make a variable accessible outside a function? Hide Signs that Meteor.js was Used How to create a showdown.js markdown extension Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Summing radio input values How to execute an action before close metro app WinJS javascript, for loop defines a dynamic variable name Getting all files in directory with ajax

Examples related to arrays

PHP array value passes to next row Use NSInteger as array index How do I show a message in the foreach loop? Objects are not valid as a React child. If you meant to render a collection of children, use an array instead Iterating over arrays in Python 3 Best way to "push" into C# array Sort Array of object by object field in Angular 6 Checking for duplicate strings in JavaScript array what does numpy ndarray shape do? How to round a numpy array?

Examples related to sorting

Sort Array of object by object field in Angular 6 Sorting a list with stream.sorted() in Java How to sort dates from Oldest to Newest in Excel? how to sort pandas dataframe from one column Reverse a comparator in Java 8 Find the unique values in a column and then sort them pandas groupby sort within groups pandas groupby sort descending order Efficiently sorting a numpy array in descending order? Swift: Sort array of objects alphabetically