4221

I have an array of JavaScript objects:

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

How can I sort them by the value of last_nom in JavaScript?

I know about sort(a,b), but that only seems to work on strings and numbers. Do I need to add a toString() method to my objects?

1
  • 5
    Case sensitive or case insensitive sort? Commented Dec 10, 2022 at 22:18

61 Answers 61

5787

It's easy enough to write your own comparison function:

function compare( a, b ) {
  if ( a.last_nom < b.last_nom ){
    return -1;
  }
  if ( a.last_nom > b.last_nom ){
    return 1;
  }
  return 0;
}

objs.sort( compare );

Or inline (c/o Marco Demaio):

objs.sort((a,b) => (a.last_nom > b.last_nom) ? 1 : ((b.last_nom > a.last_nom) ? -1 : 0))

Or simplified for numeric (c/o Andre Figueiredo):

objs.sort((a,b) => a.last_nom - b.last_nom); // b - a for reverse sort
9
  • 561
    Or inline: objs.sort(function(a,b) {return (a.last_nom > b.last_nom) ? 1 : ((b.last_nom > a.last_nom) ? -1 : 0);} ); Commented Feb 24, 2010 at 18:29
  • 45
    Official docs: developer.mozilla.org/en/JavaScript/Reference/Global_Objects/… Commented May 18, 2012 at 9:11
  • 312
    return a.last_nom.localeCompare(b.last_nom) will work, too.
    – Cerbrus
    Commented Feb 14, 2013 at 10:37
  • 197
    for those looking for a sort where the field is numeric, the compare function body: return a.value - b.value; (ASC) Commented Jan 8, 2014 at 12:06
  • 2
    You can convert a string to a number by using charCodeAt, then use the numeric inline above for a more concise one liner: objs.sort((a,b) => a.last_nom.charCodeAt(0) - b.last_nom.charCodeAt(0));. This avoids the ugly nested ternary.
    – tbatch
    Commented Apr 7, 2022 at 17:30
1110

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) {
        /* next line works with strings and numbers, 
         * and you may want to customize it to your needs
         */
        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"));

Subclassing Array

For the lucky among us who can use ES6, which allows extending the native objects:

class MyArray extends Array {
    sortBy(...args) {
        return this.sort(dynamicSortMultiple(...args));
    }
}

That would enable this:

MyArray.from(People).sortBy("Name", "-Surname");
5
  • 11
    Nice. There is now a Typescript version of this answer: stackoverflow.com/a/68279093/8910547. Stay (type) safe! 😉
    – Inigo
    Commented Jul 7, 2021 at 2:30
  • You shouldn't ever extend Array.
    – zero_cool
    Commented Jul 1, 2022 at 22:27
  • 4
    @zero_cool Array isn't being extended here (prototype stays the same), it's extended from. You indeed shouldn't change the prototype of native objects, but as I said, that's not what happens here.
    – Ege Özcan
    Commented Jul 3, 2022 at 6:58
  • not testing for null
    – serge
    Commented Sep 30, 2022 at 9:46
  • @serge any comparison of strings and nulls will result to false, putting null values at the end. if you change the a[property] < b[property] to a[property].localeCompare(b[property]), you can do a[property]?.localeCompare(b[property]) ?? 1 (take b as first if a has empty in property, and localeCompare will return -1 anyway if b has null at property -- illogical when both null though, so check for that as well maybe)
    – Ege Özcan
    Commented Oct 1, 2022 at 17:46
1086

In ES6/ES2015 or later you can do it this way:

objs.sort((a, b) => a.last_nom.localeCompare(b.last_nom));

Prior to ES6/ES2015

objs.sort(function(a, b) {
    return a.last_nom.localeCompare(b.last_nom)
});
3
  • 2
    If the values are numeric, you don't need localeCompare. You can use the standard > operator - like mentioned in the answer by @muasif80 - stackoverflow.com/a/67992215/6908282
    – Gangula
    Commented Oct 29, 2021 at 19:29
  • Does not work as for 14.5in x 20in v/s 14in x 4370ft . Any other logic I can use?
    – Yogesh
    Commented Mar 12, 2024 at 13:52
  • @Yogesh Well localeCompare is not a magic function that knows how to interpret data within a string. It just does a lexicographical string comparison. In your case, the "i" character comes after the "." character, so it seems backwards to a human. But that's the best you can hope for in a simple string comparison. Commented Feb 20 at 19:37
247

Underscore.js

Use Underscore.js]. It’s 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');
8
  • 23
    David, could you edit the answer to say, var sortedObjs = _.sortBy( objs, 'first_nom' );. objs will not be sorted itself as a result of this. The function will return a sorted array. That would make it more explicit.
    – Jess
    Commented Jan 9, 2014 at 4:01
  • 13
    To reverse sort: var reverseSortedObjs = _.sortBy( objs, 'first_nom' ).reverse();
    – Erdal G.
    Commented Jan 31, 2016 at 10:43
  • 2
    you need to load the javascript libary "underscore": <script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"> </script>
    – and-bri
    Commented May 29, 2017 at 18:28
  • 7
    Also available in Lodash for the ones who prefer that one
    – WoJ
    Commented Apr 17, 2018 at 10:49
  • 7
    In lodash this would be the same: var sortedObjs = _.sortBy( objs, 'first_nom' ); or if you want it in a different order: var sortedObjs = _.orderBy( objs, ['first_nom'],['dsc'] ); Commented Nov 15, 2018 at 19:10
156

Case sensitive

arr.sort((a, b) => a.name > b.name ? 1 : -1);

Case Insensitive

arr.sort((a, b) => a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1);

Useful Note

If no change in order (in case of the same strings) then the condition > will fail and -1 will be returned. But if strings are same then returning 1 or -1 will result in correct output

The other option could be to use >= operator instead of >


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


// Define a couple of sorting callback functions, one with hardcoded sort key and the other with an argument sort key
const sorter1 = (a, b) => a.last_nom.toLowerCase() > b.last_nom.toLowerCase() ? 1 : -1;
const sorter2 = (sortBy) => (a, b) => a[sortBy].toLowerCase() > b[sortBy].toLowerCase() ? 1 : -1;

objs.sort(sorter1);
console.log("Using sorter1 - Hardcoded sort property last_name", objs);

objs.sort(sorter2('first_nom'));
console.log("Using sorter2 - passed param sortBy='first_nom'", objs);

objs.sort(sorter2('last_nom'));
console.log("Using sorter2 - passed param sortBy='last_nom'", objs);

6
  • The case sensitive approach is a good shorthand - especially if the values are numeric or date.
    – Gangula
    Commented Oct 29, 2021 at 19:26
  • TIP: if you'd like to reverse the order, you can simply swap -1 and 1 for eg: from 1 : -1 to -1 : 1
    – Gangula
    Commented Oct 29, 2021 at 19:32
  • What about changing (a, b) to (b, a) :)
    – muasif80
    Commented Oct 30, 2021 at 17:29
  • 2
    Yes, that works too. I just find swapping 1 & -1 more straight forward and logical.
    – Gangula
    Commented Oct 30, 2021 at 18:03
  • 6
    Returning non-zero for equal items violates the compareFn contract. The issue may not manifest in current implementations of sort(), but is not future-proof and/or may affect performance. Commented Dec 22, 2022 at 19:56
89

If you have duplicate last names you might sort those by first name-

obj.sort(function(a,b){
  if(a.last_nom< b.last_nom) return -1;
  if(a.last_nom >b.last_nom) return 1;
  if(a.first_nom< b.first_nom) return -1;
  if(a.first_nom >b.first_nom) return 1;
  return 0;
});
3
  • @BadFeelingAboutThis what does returning either -1 or 1 mean? I understand that -1 literally means that A is less than B just by the syntax, but why use a 1 or -1? I see everyone is using those numbers as return values, but why? Thanks.
    – Chris22
    Commented Aug 22, 2018 at 6:15
  • 1
    @Chris22 a negative number returned means that b should come after a in the array. If a positive number is returned, it means a should come after b. If 0 is returned, it means they are considered equal. You can always read the documentation: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… Commented Aug 22, 2018 at 16:54
  • 1
    @BadFeelingAboutThis thanks, for the explanation and the link. Believe it or not, I googled various snippets of code using the 1, 0, -1 before I asked this here. I just wasn't finding the info I needed.
    – Chris22
    Commented Aug 22, 2018 at 17:07
72

As of 2018 there is a much shorter and elegant solution. Just use. Array.prototype.sort().

Example:

var items = [
  { name: 'Edward', value: 21 },
  { name: 'Sharpe', value: 37 },
  { name: 'And', value: 45 },
  { name: 'The', value: -12 },
  { name: 'Magnetic', value: 13 },
  { name: 'Zeros', value: 37 }
];

// sort by value
items.sort(function (a, b) {
  return a.value - b.value;
});
3
  • 23
    In the question strings were used for comparison as opposed to numbers. Your answer works great for sorting by numbers, but isn't so good for comparisons by string.
    – smcstewart
    Commented Jun 18, 2018 at 10:02
  • The a.value - b.value used to compare the object's attributes (numbers in this case) can be adopted for the various times of data. For example, regex can be used to compare each pair of the neighboring strings.
    – 0leg
    Commented Mar 26, 2019 at 9:10
  • This implementation is quite good if you need to sort it by ID. Yeah , you have suggested to use regex to compare neighbouring string which makes solution more complicated whereas purpose of this simpliefied version will be otherwise if regex is used along with given solution. Simplicity is the best. Commented Dec 10, 2019 at 8:29
70

Simple and quick solution to this problem using prototype inheritance:

Array.prototype.sortBy = function(p) {
  return this.slice(0).sort(function(a,b) {
    return (a[p] > b[p]) ? 1 : (a[p] < b[p]) ? -1 : 0;
  });
}

Example / Usage

objs = [{age:44,name:'vinay'},{age:24,name:'deepak'},{age:74,name:'suresh'}];

objs.sortBy('age');
// Returns
// [{"age":24,"name":"deepak"},{"age":44,"name":"vinay"},{"age":74,"name":"suresh"}]

objs.sortBy('name');
// Returns
// [{"age":24,"name":"deepak"},{"age":74,"name":"suresh"},{"age":44,"name":"vinay"}]

Update: No longer modifies original array.

5
  • 8
    It dosn't just return another array. but actually sorts the original one!. Commented Jul 21, 2012 at 5:43
  • 1
    If you want to make sure you are using a natural sort with numbers (i.e., 0,1,2,10,11 etc...) use parseInt with the Radix set. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… so: return (parseInt(a[p],10) > parseInt(b[p],10)) ? 1 : (parseInt(a[p],10) < parseInt(b[p],10)) ? -1 : 0;
    – Paul
    Commented May 11, 2015 at 19:14
  • @codehuntr Thanks for correcting it. but i guess instead of making sort function to do this sensitization, it's better if we make a separate function to fix data types. Because sort function can't not tell which property will contain what kind of data. :) Commented May 21, 2015 at 16:55
  • 1
    i think this will only work on some prop types.. you'd want to add date/string handling etc.. i.e. if type is string use return a.localCompare(b) etc. etc..
    – Sonic Soul
    Commented Nov 9, 2021 at 14:41
  • I assume the purpose of .slice(0) is to make a shallow copy of the array.
    – emallove
    Commented Jan 6, 2023 at 19:05
69

Old answer that is not correct:

arr.sort((a, b) => a.name > b.name)

UPDATE

From Beauchamp's comment:

arr.sort((a, b) => a.name < b.name ? -1 : (a.name > b.name ? 1 : 0))

More readable format:

arr.sort((a, b) => {
  if (a.name < b.name) return -1
  return a.name > b.name ? 1 : 0
})

Without nested ternaries:

arr.sort((a, b) => a.name < b.name ? - 1 : Number(a.name > b.name))

Explanation: Number() will cast true to 1 and false to 0.

5
  • 1
    It works, but the result is unstable for some reason Commented Feb 3, 2018 at 19:55
  • 17
    This should do it: arr.sort((a, b) => a.name < b.name ? -1 : (a.name > b.name ? 1 : 0)) Commented Aug 29, 2018 at 15:33
  • 3
    @Jean-FrançoisBeauchamp, your solution works perfectly fine and much better. Commented Jan 22, 2019 at 13:49
  • the 3rd one with Number is straightforward and nice !
    – Kojo
    Commented Jan 15, 2020 at 16:43
  • 2
    Why would arr.sort((a, b) => a.name > b.name ? 1 : -1 will not work? For Strings i have tested this works great. If you want case insensitive then use a.name.toLowerCase() and b.name.toLowerCase()
    – muasif80
    Commented Jun 15, 2021 at 19:08
45

You can use Easiest Way: Lodash

(https://lodash.com/docs/4.17.10#orderBy)

This method is like _.sortBy except that it allows specifying the sort orders of the iteratees to sort by. If orders is unspecified, all values are sorted in ascending order. Otherwise, specify an order of "desc" for descending or "asc" for ascending sort order of corresponding values.

Arguments

collection (Array|Object): The collection to iterate over. [iteratees=[_.identity]] (Array[]|Function[]|Object[]|string[]): The iteratees to sort by. [orders] (string[]): The sort orders of iteratees.

Returns

(Array): Returns the new sorted array.


var _ = require('lodash');
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"}
    ];
    
_.orderBy(homes, ['city', 'state', 'zip'], ['asc', 'desc', 'asc']);
1
  • Here, this does not pass negative numbers. Commented Aug 15, 2023 at 15:11
45

Lodash (a superset of Underscore.js).

It's good not to add a framework for every simple piece of logic, but relying on well tested utility frameworks can speed up development and reduce the amount of bugs.

Lodash produces very clean code and promotes a more functional programming style. In one glimpse, it becomes clear what the intent of the code is.

The OP's issue can simply be solved as:

const sortedObjs = _.sortBy(objs, 'last_nom');

More information? For example, we have the following nested object:

const users = [
  { 'user': {'name':'fred', 'age': 48}},
  { 'user': {'name':'barney', 'age': 36 }},
  { 'user': {'name':'wilma'}},
  { 'user': {'name':'betty', 'age': 32}}
];

We now can use the _.property shorthand user.age to specify the path to the property that should be matched. We will sort the user objects by the nested age property. Yes, it allows for nested property matching!

const sortedObjs = _.sortBy(users, ['user.age']);

Want it reversed? No problem. Use _.reverse.

const sortedObjs = _.reverse(_.sortBy(users, ['user.age']));

Want to combine both using chain?

const { chain } = require('lodash');
const sortedObjs = chain(users).sortBy('user.age').reverse().value();

Or when do you prefer flow over chain?

const { flow, reverse, sortBy } = require('lodash/fp');
const sortedObjs = flow([sortBy('user.age'), reverse])(users);
42

I haven't seen this particular approach suggested, so here's a terse comparison method I like to use that works for both string and number types:

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

const sortBy = fn => {
  const cmp = (a, b) => -(a < b) || +(a > b);
  return (a, b) => cmp(fn(a), fn(b));
};

const getLastName = o => o.last_nom;
const sortByLastName = sortBy(getLastName);

objs.sort(sortByLastName);
console.log(objs.map(getLastName));

Explanation of sortBy()

sortBy() accepts a fn that selects a value from an object to use in comparison, and returns a function that can be passed to Array.prototype.sort(). In this example, we're comparing o.last_nom. Whenever we receive two objects such as

a = { first_nom: 'Lazslo', last_nom: 'Jamf' }
b = { first_nom: 'Pig', last_nom: 'Bodine' }

we compare them with (a, b) => cmp(fn(a), fn(b)). Given that

fn = o => o.last_nom

we can expand the comparison function to (a, b) => cmp(a.last_nom, b.last_nom). Because of the way logical OR (||) works in JavaScript, cmp(a.last_nom, b.last_nom) is equivalent to

if (a.last_nom < b.last_nom) return -1;
if (a.last_nom > b.last_nom) return 1;
return 0;

Incidentally, this is called the three-way comparison "spaceship" (<=>) operator in other languages.

Finally, here's the ES5-compatible syntax without using arrow functions:

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

function sortBy(fn) {
  function cmp(a, b) { return -(a < b) || +(a > b); }
  return function (a, b) { return cmp(fn(a), fn(b)); };
}

function getLastName(o) { return o.last_nom; }
var sortByLastName = sortBy(getLastName);

objs.sort(sortByLastName);
console.log(objs.map(getLastName));

4
  • I like this approach but I think using the shorthand of -(fa < fb) || +(fa > fb) is a mistake here. That's multiple statements being condensed into one line of code. The alternative, written with an if statement, would be much more readable whilst still being fairly concise. I think it's a mistake to sacrifice readability for prettiness.
    – MSOACC
    Commented Jul 9, 2020 at 11:02
  • @MSOACC thanks for your opinion but I respectfully disagree. Other languages implement a three-way comparison operator that performs the same comparison, so just think of it conceptually as fa <=> fb. Commented Jul 9, 2020 at 14:55
  • Hey Patrick, I like your answer but it's would work properly only with English characters (const cmp = (a, b) => -(a < b) || +(a > b);) Think of ["ä", "a", "c", "b"].sort(cmp) => ["a", "b", "c", "ä"], where ä is pushed to the end. Instead you should probably update the comparison function to: const cmp = (a, b) => a.localeCompare(b); => ["a", "ä", "b", "c"] Cheers and thanks for the answer ;-)
    – rjanjic
    Commented Oct 9, 2020 at 13:48
  • @rjanjic thanks for the feedback. I'm aware that it sorts based on the code point of the character in unicode. However changing it to use localeCompare removes the ability to sort numbers, and is also significantly slower. Commented Oct 9, 2020 at 19:22
36

Instead of using a custom comparison function, you could also create an object type with custom toString() method (which is invoked by the default comparison function):

function Person(firstName, lastName) {
    this.firtName = firstName;
    this.lastName = lastName;
}

Person.prototype.toString = function() {
    return this.lastName + ', ' + this.firstName;
}

var persons = [ new Person('Lazslo', 'Jamf'), ...]
persons.sort();
35

There are many good answers here, but I would like to point out that they can be extended very simply to achieve a lot more complex sorting. The only thing you have to do is to use the OR operator to chain comparison functions like this:

objs.sort((a,b)=> fn1(a,b) || fn2(a,b) || fn3(a,b) )

Where fn1, fn2, ... are the sort functions which return [-1,0,1]. This results in "sorting by fn1" and "sorting by fn2" which is pretty much equal to ORDER BY in SQL.

This solution is based on the behaviour of || operator which evaluates to the first evaluated expression which can be converted to true.

The simplest form has only one inlined function like this:

// ORDER BY last_nom
objs.sort((a,b)=> a.last_nom.localeCompare(b.last_nom) )

Having two steps with last_nom,first_nom sort order would look like this:

// ORDER_BY last_nom, first_nom
objs.sort((a,b)=> a.last_nom.localeCompare(b.last_nom) ||
                  a.first_nom.localeCompare(b.first_nom)  )

A generic comparison function could be something like this:

// ORDER BY <n>
let cmp = (a,b,n)=>a[n].localeCompare(b[n])

This function could be extended to support numeric fields, case-sensitivity, arbitrary data types, etc.

You can use them by chaining them by sort priority:

// ORDER_BY last_nom, first_nom
objs.sort((a,b)=> cmp(a,b, "last_nom") || cmp(a,b, "first_nom") )
// ORDER_BY last_nom, first_nom DESC
objs.sort((a,b)=> cmp(a,b, "last_nom") || -cmp(a,b, "first_nom") )
// ORDER_BY last_nom DESC, first_nom DESC
objs.sort((a,b)=> -cmp(a,b, "last_nom") || -cmp(a,b, "first_nom") )

The point here is that pure JavaScript with functional approach can take you a long way without external libraries or complex code. It is also very effective, since no string parsing have to be done.

0
33

Try this:

Up to ES5
// Ascending sort
items.sort(function (a, b) {
   return a.value - b.value;
});


// Descending sort
items.sort(function (a, b) {
   return b.value - a.value;
});
In ES6 and above
// Ascending sort
items.sort((a, b) => a.value - b.value);

// Descending sort
items.sort((a, b) => b.value - a.value);
3
  • 2
    best and easy solution
    – Omar Hasan
    Commented Mar 16, 2020 at 9:21
  • 3
    Doesn't work for me, tried other solutions that does indeed work but this one doesn't. Attempted to sort by strings.
    – Thorvald
    Commented Mar 31, 2022 at 22:17
  • Does not correctly pass negative numbers Commented Aug 15, 2023 at 15:06
30

Use JavaScript sort method

The sort method can be modified to sort anything like an array of numbers, strings and even objects using a compare function.

A compare function is passed as an optional argument to the sort method.

This compare function accepts 2 arguments generally called a and b. Based on these 2 arguments you can modify the sort method to work as you want.

  1. If the compare function returns less than 0, then the sort() method sorts a at a lower index than b. Simply a will come before b.
  2. If the compare function returns equal to 0, then the sort() method leaves the element positions as they are.
  3. If the compare function returns greater than 0, then the sort() method sorts a at greater index than b. Simply a will come after b.

Use the above concept to apply on your object where a will be your object property.

var objs = [
  { first_nom: 'Lazslo', last_nom: 'Jamf' },
  { first_nom: 'Pig', last_nom: 'Bodine' },
  { first_nom: 'Pirate', last_nom: 'Prentice' }
];
function compare(a, b) {
  if (a.last_nom > b.last_nom) return 1;
  if (a.last_nom < b.last_nom) return -1;
  return 0;
}
objs.sort(compare);
console.log(objs)
// for better look use console.table(objs)
output

29

Example Usage:

objs.sort(sortBy('last_nom'));

Script:

/**
 * @description
 * Returns a function which will sort an
 * array of objects by the given key.
 *
 * @param  {String}  key
 * @param  {Boolean} reverse
 * @return {Function}
 */
const sortBy = (key, reverse) => {

  // Move smaller items towards the front
  // or back of the array depending on if
  // we want to sort the array in reverse
  // order or not.
  const moveSmaller = reverse ? 1 : -1;

  // Move larger items towards the front
  // or back of the array depending on if
  // we want to sort the array in reverse
  // order or not.
  const moveLarger = reverse ? -1 : 1;

  /**
   * @param  {*} a
   * @param  {*} b
   * @return {Number}
   */
  return (a, b) => {
    if (a[key] < b[key]) {
      return moveSmaller;
    }
    if (a[key] > b[key]) {
      return moveLarger;
    }
    return 0;
  };
};
3
  • thank you for breaking this down, I am trying to understand why digits 1, 0, -1 are used for sort ordering. Even with your explanation above, which looks very good-- I'm still not quite understanding it. I always think of -1 as when using array length property, i.e.: arr.length = -1 means that the item isn't found. I'm probably mixing things up here, but could you help me understand why digits 1, 0, -1 are used to determine order? Thanks.
    – Chris22
    Commented Aug 22, 2018 at 6:23
  • 1
    This isn't entirely accurate but it might help to think of it like this: the function passed to array.sort is called once for each item in the array, as the argument named "a". The return value of each function call is how the index (current position number) of item "a" should be altered compared to the next item "b". The index dictates the order of the array (0, 1, 2 etc) So if "a" is at index 5 and you return -1 then 5 + -1 == 4 (move it nearer front) 5 + 0 == 5 (keep it where it is) etc. It walks the array comparing 2 neighbours each time until it reaches the end, leaving a sorted array. Commented Aug 23, 2018 at 9:09
  • 1
    thank you for taking the time to explain this further. So using your explanation and the MDN Array.prototype.sort, I'll tell you what I'm thinking of this: in comparison to a and b, if a is greater than b add 1 to the index of a and place it behind b, if a is less than b, subtract 1 from a and place it in front of b. If a and b are the same, add 0 to a and leave it where it is.
    – Chris22
    Commented Aug 23, 2018 at 16:28
27

Write short code:

objs.sort((a, b) => a.last_nom > b.last_nom ? 1 : -1)
7
  • 2
    what if values are equal? considering there are 3 values you can return - 1, -1, 0 Commented Apr 15, 2021 at 9:36
  • @SomeoneSpecial so what? The result will be the same
    – artem
    Commented Apr 15, 2021 at 11:15
  • What does 1 || -1 mean ? Commented Jun 26, 2021 at 17:53
  • @KaleemElahi If I understand correctly, he's using it as a bit mask. If a.last_nom > b.last_nom THEN 1 ELSE -1. Effectively moving an item up or down based on the comparison. Commented Nov 10, 2021 at 23:22
  • 1
    There is no bit mask, expression a>b && 1|| -1 is equal to a> b ? 1 : -1, operator && returns first logical false value, operator || returns first logical true value.
    – artem
    Commented Nov 11, 2021 at 6:19
25

I didn't see any implementation similar to mine. This version is based on the Schwartzian transform idiom.

function sortByAttribute(array, ...attrs) {
  // Generate an array of predicate-objects containing
  // 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: 'Mashraki',          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'));
0
20

Sorting objects with Intl.Collator for the specific case when you want natural string sorting (i.e. ["1","2","10","11","111"]).

const files = [
 {name: "1.mp3", size: 123},
 {name: "10.mp3", size: 456},
 {name: "100.mp3", size: 789},
 {name: "11.mp3", size: 123},
 {name: "111.mp3", size: 456},
 {name: "2.mp3", size: 789},
];

const naturalCollator = new Intl.Collator(undefined, {numeric: true, sensitivity: 'base'});

files.sort((a, b) => naturalCollator.compare(a.name, b.name));

console.log(files);

Note: the undefined constructor argument for Intl.Collator represents the locale, which can be an explicit ISO 639-1 language code such as en, or the system default locale when undefined.

Browser support for Intl.Collator

0
18

Sorting (more) Complex Arrays of Objects

Since you probably encounter more complex data structures like this array, I would expand the solution.

TL;DR

Are more pluggable version based on @ege-Özcan's very lovely answer.

Problem

I encountered the below and couldn't change it. I also did not want to flatten the object temporarily. Nor did I want to use underscore / lodash, mainly for performance reasons and the fun to implement it myself.

var People = [
   {Name: {name: "Name", surname: "Surname"}, Middlename: "JJ"},
   {Name: {name: "AAA", surname: "ZZZ"}, Middlename:"Abrams"},
   {Name: {name: "Name", surname: "AAA"}, Middlename: "Wars"}
];

Goal

The goal is to sort it primarily by People.Name.name and secondarily by People.Name.surname

Obstacles

Now, in the base solution uses bracket notation to compute the properties to sort for dynamically. Here, though, we would have to construct the bracket notation dynamically also, since you would expect some like People['Name.name'] would work - which doesn't.

Simply doing People['Name']['name'], on the other hand, is static and only allows you to go down the n-th level.

Solution

The main addition here will be to walk down the object tree and determine the value of the last leaf, you have to specify, as well as any intermediary leaf.

var People = [
   {Name: {name: "Name", surname: "Surname"}, Middlename: "JJ"},
   {Name: {name: "AAA", surname: "ZZZ"}, Middlename:"Abrams"},
   {Name: {name: "Name", surname: "AAA"}, Middlename: "Wars"}
];

People.sort(dynamicMultiSort(['Name','name'], ['Name', '-surname']));
// Results in...
// [ { Name: { name: 'AAA', surname: 'ZZZ' }, Middlename: 'Abrams' },
//   { Name: { name: 'Name', surname: 'Surname' }, Middlename: 'JJ' },
//   { Name: { name: 'Name', surname: 'AAA' }, Middlename: 'Wars' } ]

// same logic as above, but strong deviation for dynamic properties 
function dynamicSort(properties) {
  var sortOrder = 1;
  // determine sort order by checking sign of last element of array
  if(properties[properties.length - 1][0] === "-") {
    sortOrder = -1;
    // Chop off sign
    properties[properties.length - 1] = properties[properties.length - 1].substr(1);
  }
  return function (a,b) {
    propertyOfA = recurseObjProp(a, properties)
    propertyOfB = recurseObjProp(b, properties)
    var result = (propertyOfA < propertyOfB) ? -1 : (propertyOfA > propertyOfB) ? 1 : 0;
    return result * sortOrder;
  };
}

/**
 * Takes an object and recurses down the tree to a target leaf and returns it value
 * @param  {Object} root - Object to be traversed.
 * @param  {Array} leafs - Array of downwards traversal. To access the value: {parent:{ child: 'value'}} -> ['parent','child']
 * @param  {Number} index - Must not be set, since it is implicit.
 * @return {String|Number}       The property, which is to be compared by sort.
 */
function recurseObjProp(root, leafs, index) {
  index ? index : index = 0
  var upper = root
  // walk down one level
  lower = upper[leafs[index]]
  // Check if last leaf has been hit by having gone one step too far.
  // If so, return result from last step.
  if (!lower) {
    return upper
  }
  // Else: recurse!
  index++
  // HINT: Bug was here, for not explicitly returning function
  // https://stackoverflow.com/a/17528613/3580261
  return recurseObjProp(lower, leafs, index)
}

/**
 * Multi-sort your array by a set of properties
 * @param {...Array} Arrays to access values in the form of: {parent:{ child: 'value'}} -> ['parent','child']
 * @return {Number} Number - number for sort algorithm
 */
function dynamicMultiSort() {
  var args = Array.prototype.slice.call(arguments); // slight deviation to base

  return function (a, b) {
    var i = 0, result = 0, numberOfProperties = args.length;
    // REVIEW: slightly verbose; maybe no way around because of `.sort`-'s nature
    // Consider: `.forEach()`
    while(result === 0 && i < numberOfProperties) {
      result = dynamicSort(args[i])(a, b);
      i++;
    }
    return result;
  }
}

Example

Working example on JSBin

1
  • 4
    Why? This is not the answer to original question and "the goal" could be solved simply with People.sort((a,b)=>{ return a.Name.name.localeCompare(b.Name.name) || a.Name.surname.localeCompare(b.Name.surname) }) Commented May 3, 2016 at 16:02
16

Combining Ege's dynamic solution with Vinay's idea, you get a nice robust solution:

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

  function _getSortFunc() {
    if (arguments.length == 0) {
      throw "Zero length arguments not allowed for Array.sortBy()";
    }
    var args = arguments;
    return function(a, b) {
      for (var result = 0, i = 0; result == 0 && i < args.length; i++) {
        result = _sortByAttr(args[i])(a, b);
      }
      return result;
    }
  }
  return this.sort(_getSortFunc.apply(null, arguments));
}

Usage:

  // Utility for printing objects
  Array.prototype.print = function(title) {
    console.log("************************************************************************");
    console.log("**** " + title);
    console.log("************************************************************************");
    for (var i = 0; i < this.length; i++) {
      console.log("Name: " + this[i].FirstName, this[i].LastName, "Age: " + this[i].Age);
    }
  }

// Setup sample data
var arrObj = [{
    FirstName: "Zach",
    LastName: "Emergency",
    Age: 35
  },
  {
    FirstName: "Nancy",
    LastName: "Nurse",
    Age: 27
  },
  {
    FirstName: "Ethel",
    LastName: "Emergency",
    Age: 42
  },
  {
    FirstName: "Nina",
    LastName: "Nurse",
    Age: 48
  },
  {
    FirstName: "Anthony",
    LastName: "Emergency",
    Age: 44
  },
  {
    FirstName: "Nina",
    LastName: "Nurse",
    Age: 32
  },
  {
    FirstName: "Ed",
    LastName: "Emergency",
    Age: 28
  },
  {
    FirstName: "Peter",
    LastName: "Physician",
    Age: 58
  },
  {
    FirstName: "Al",
    LastName: "Emergency",
    Age: 51
  },
  {
    FirstName: "Ruth",
    LastName: "Registration",
    Age: 62
  },
  {
    FirstName: "Ed",
    LastName: "Emergency",
    Age: 38
  },
  {
    FirstName: "Tammy",
    LastName: "Triage",
    Age: 29
  },
  {
    FirstName: "Alan",
    LastName: "Emergency",
    Age: 60
  },
  {
    FirstName: "Nina",
    LastName: "Nurse",
    Age: 54
  }
];

//Unit Tests
arrObj.sortBy("LastName").print("LastName Ascending");
arrObj.sortBy("-LastName").print("LastName Descending");
arrObj.sortBy("LastName", "FirstName", "-Age").print("LastName Ascending, FirstName Ascending, Age Descending");
arrObj.sortBy("-FirstName", "Age").print("FirstName Descending, Age Ascending");
arrObj.sortBy("-Age").print("Age Descending");

1
  • 1
    Thanks for the idea! By the way, please do not encourage people to change the Array Prototype (see the warning at the end of my example).
    – Ege Özcan
    Commented May 10, 2013 at 14:51
15

One more option:

var someArray = [...];

function generateSortFn(prop, reverse) {
    return function (a, b) {
        if (a[prop] < b[prop]) return reverse ? 1 : -1;
        if (a[prop] > b[prop]) return reverse ? -1 : 1;
        return 0;
    };
}

someArray.sort(generateSortFn('name', true));

It sorts ascending by default.

3
  • 1
    Slightly changed version for sorting by multiple fields is here if needed: stackoverflow.com/questions/6913512/… Commented Dec 1, 2016 at 12:37
  • it looks like it could be the next one: export function generateSortFn( prop: string, reverse: boolean = false ): (...args: any) => number { return (a, b) => { return a[prop] < b[prop] ? reverse ? 1 : -1 : a[prop] > b[prop] ? reverse ? -1 : 1 : 0; }; }
    – Den Kerny
    Commented May 23, 2020 at 4:29
  • agreed, but in some case i haven't got needs to look at utility functions.
    – Den Kerny
    Commented May 25, 2020 at 2:00
13

A simple way:

objs.sort(function(a,b) {
  return b.last_nom.toLowerCase() < a.last_nom.toLowerCase();
});

See that '.toLowerCase()' is necessary to prevent erros in comparing strings.

3
  • 5
    You could use arrow functions to let the code a little more elegant: objs.sort( (a,b) => b.last_nom.toLowerCase() < a.last_nom.toLowerCase() );
    – Sertage
    Commented May 24, 2017 at 15:04
  • This is wrong for the same reason as explained here. Commented Jul 18, 2018 at 9:24
  • 2
    Arrow functions are not ES5-worthy. Tons of engines still are restricted to ES5. In my case, I find the answer above significantly better since I'm on an ES5 engine (forced by my company)
    – dylanh724
    Commented Jul 21, 2018 at 8:50
13

Warning!
Using this solution is not recommended as it does not result in a sorted array. It is being left here for future reference, because the idea is not rare.

objs.sort(function(a,b){return b.last_nom>a.last_nom})
1
  • 3
    Actually it didn't seem to work, had to use the accepted answer. It wasn't sorting correctly.
    – madprops
    Commented Feb 21, 2017 at 11:15
13

This is my take on this:

The order parameter is optional and defaults to "ASC" for ascending order.

It works on accented characters and it's case insensitive.

Note: It sorts and returns the original array.

function sanitizeToSort(str) {
  return str
    .normalize('NFD')                   // Remove accented and diacritics
    .replace(/[\u0300-\u036f]/g, '')    // Remove accented and diacritics
    .toLowerCase()                      // Sort will be case insensitive
  ;
}

function sortByProperty(arr, property, order="ASC") {
  arr.forEach((item) => item.tempProp = sanitizeToSort(item[property]));
  arr.sort((a, b) => order === "ASC" ?
      a.tempProp > b.tempProp ?  1 : a.tempProp < b.tempProp ? -1 : 0
    : a.tempProp > b.tempProp ? -1 : a.tempProp < b.tempProp ?  1 : 0
  );
  arr.forEach((item) => delete item.tempProp);
  return arr;
}

Snippet

function sanitizeToSort(str) {
  return str
    .normalize('NFD')                   // Remove accented characters
    .replace(/[\u0300-\u036f]/g, '')    // Remove diacritics
    .toLowerCase()
  ;
}

function sortByProperty(arr, property, order="ASC") {
  arr.forEach((item) => item.tempProp = sanitizeToSort(item[property]));
  arr.sort((a, b) => order === "ASC" ?
      a.tempProp > b.tempProp ?  1 : a.tempProp < b.tempProp ? -1 : 0
    : a.tempProp > b.tempProp ? -1 : a.tempProp < b.tempProp ?  1 : 0
  );
  arr.forEach((item) => delete item.tempProp);
  return arr;
}

const rockStars = [
  { name: "Axl",
    lastname: "Rose" },
  { name: "Elthon",
    lastname: "John" },
  { name: "Paul",
    lastname: "McCartney" },
  { name: "Lou",
    lastname: "Reed" },
  { name: "freddie",             // Works on lower/upper case
    lastname: "mercury" },
  { name: "Ámy",                 // Works on accented characters too
    lastname: "winehouse"}

];

sortByProperty(rockStars, "name");

console.log("Ordered by name A-Z:");
rockStars.forEach((item) => console.log(item.name + " " + item.lastname));

sortByProperty(rockStars, "lastname", "DESC");

console.log("\nOrdered by lastname Z-A:");
rockStars.forEach((item) => console.log(item.lastname + ", " + item.name));

2
  • not working if the list contain name in the combination of uppercase and lowercase character Commented Aug 27, 2020 at 14:37
  • @AnkeshPandey Thanks for pointing that out. I've fixed it. Commented Sep 7, 2020 at 8:10
12

Given the original example:

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

Sort by multiple fields:

objs.sort(function(left, right) {
    var last_nom_order = left.last_nom.localeCompare(right.last_nom);
    var first_nom_order = left.first_nom.localeCompare(right.first_nom);
    return last_nom_order || first_nom_order;
});

Notes

  • a.localeCompare(b) is universally supported and returns -1,0,1 if a<b,a==b,a>b respectively.
  • || in the last line gives last_nom priority over first_nom.
  • Subtraction works on numeric fields: var age_order = left.age - right.age;
  • Negate to reverse order, return -last_nom_order || -first_nom_order || -age_order;
12

A simple function that sorts an array of object by a property:

function sortArray(array, property, direction) {
    direction = direction || 1;
    array.sort(function compare(a, b) {
        let comparison = 0;
        if (a[property] > b[property]) {
            comparison = 1 * direction;
        } else if (a[property] < b[property]) {
            comparison = -1 * direction;
        }
        return comparison;
    });
    return array; // Chainable
}

Usage:

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

sortArray(objs, "last_nom"); // Asc
sortArray(objs, "last_nom", -1); // Desc
1
  • This solution worked perfectly for me to sort bi-directional. Thank u
    – Coder0997
    Commented Mar 23, 2020 at 17:23
12

Additional desc parameters for Ege Özcan's code:

function dynamicSort(property, desc) {
    if (desc) {
        return function (a, b) {
            return (a[property] > b[property]) ? -1 : (a[property] < b[property]) ? 1 : 0;
        }
    }
    return function (a, b) {
        return (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;
    }
}
1
  • 1
    What is "desc" for? "descending"? "descriptor"? Something else? Commented Dec 10, 2022 at 22:27
11

You may need to convert them to the lowercase form in order to prevent from confusion.

objs.sort(function (a, b) {

    var nameA = a.last_nom.toLowerCase(), nameB = b.last_nom.toLowerCase()

    if (nameA < nameB)
      return -1;
    if (nameA > nameB)
      return 1;
    return 0;  // No sorting
})

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.