4229

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 oflast_nom in JavaScript?

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

Adam's user avatar
Adam
5,2933 gold badges33 silver badges64 bronze badges
askedJul 15, 2009 at 3:17
Tyrone Slothrop's user avatar
1
  • 5
    Case sensitive or case insensitive sort?CommentedDec 10, 2022 at 22:18

61 Answers61

5800

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
Peter Mortensen's user avatar
Peter Mortensen
31.4k22 gold badges110 silver badges134 bronze badges
answeredJul 15, 2009 at 3:35
Wogan's user avatar
Sign up to request clarification or add additional context in comments.

9 Comments

Or inline: objs.sort(function(a,b) {return (a.last_nom > b.last_nom) ? 1 : ((b.last_nom > a.last_nom) ? -1 : 0);} );
return a.last_nom.localeCompare(b.last_nom) will work, too.
for those looking for a sort where the field is numeric, the compare function body:return a.value - b.value; (ASC)
You can convert a string to a number by usingcharCodeAt, 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.
|
1112

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 thatit 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");
answeredJan 21, 2011 at 15:03
Ege Özcan's user avatar

5 Comments

Nice. There is now a Typescript version of this answer:stackoverflow.com/a/68279093/8910547. Stay (type) safe! 😉
You shouldn't ever extend Array.
@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.
not testing for null
@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)
1099

InES6/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)});
Peter Mortensen's user avatar
Peter Mortensen
31.4k22 gold badges110 silver badges134 bronze badges
answeredJan 29, 2016 at 19:44
Vlad Bezden's user avatar

3 Comments

If the values are numeric, you don't needlocaleCompare. You can use the standard> operator - like mentioned in the answer by @muasif80 -stackoverflow.com/a/67992215/6908282
Does not work as for 14.5in x 20in v/s 14in x 4370ft . Any other logic I can use?
@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.
247

Underscore.js

Use Underscore.js]. It’s small and awesome...

sortBy_.sortBy(list, iterator, [context]) Returns a sorted copy oflist, ranked in ascending order by the results of running each valuethrough iterator. Iterator may also be the string name of the propertyto 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');
Peter Mortensen's user avatar
Peter Mortensen
31.4k22 gold badges110 silver badges134 bronze badges
answeredMay 10, 2012 at 21:24
David Morrow's user avatar

8 Comments

David, could you edit the answer to say,var sortedObjs = _.sortBy( objs, 'first_nom' );.objs willnot be sorted itself as a result of this. The function willreturn a sorted array. That would make it more explicit.
To reverse sort:var reverseSortedObjs = _.sortBy( objs, 'first_nom' ).reverse();
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>
Also available inLodash for the ones who prefer that one
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'] );
|
157

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 keyconst 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);

answeredJun 15, 2021 at 19:15
muasif80's user avatar

6 Comments

The case sensitive approach is a good shorthand - especially if the values are numeric or date.
TIP: if you'd like to reverse the order, you can simply swap-1 and1 for eg: from1 : -1 to-1 : 1
What about changing(a, b) to (b, a) :)
Yes, that works too. I just find swapping1 &-1 more straight forward and logical.
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.
|
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;});
BadFeelingAboutThis's user avatar
BadFeelingAboutThis
14.4k2 gold badges35 silver badges40 bronze badges
answeredJul 15, 2009 at 4:03
kennebec's user avatar

3 Comments

@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 a negative number returned means thatb should come aftera in the array. If a positive number is returned, it meansa should come afterb. If0 is returned, it means they are considered equal. You can always read the documentation:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
@BadFeelingAboutThis thanks, for the explanation and the link. Believe it or not, I googled various snippets of code using the1, 0, -1 before I asked this here. I just wasn't finding the info I needed.
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 valueitems.sort(function (a, b) {  return a.value - b.value;});
answeredJun 4, 2018 at 15:24
0leg's user avatar

3 Comments

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.
Thea.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 neighboringstrings.
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.
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.

Web_Designer's user avatar
Web_Designer
75.1k93 gold badges210 silver badges268 bronze badges
answeredJul 10, 2012 at 11:54
Vinay Aggarwal's user avatar

5 Comments

It dosn't just return another array. but actually sorts the original one!.
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;
@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. :)
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..
I assume the purpose of.slice(0) is to make a shallow copy of the array.
69

Old answer that is not correct:

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

UPDATE

FromBeauchamp'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 casttrue to1 andfalse to0.

Peter Mortensen's user avatar
Peter Mortensen
31.4k22 gold badges110 silver badges134 bronze badges
answeredOct 20, 2017 at 12:10
Damjan Pavlica's user avatar

5 Comments

It works, but the result is unstable for some reason
This should do it:arr.sort((a, b) => a.name < b.name ? -1 : (a.name > b.name ? 1 : 0))
@Jean-FrançoisBeauchamp, your solution works perfectly fine and much better.
the 3rd one with Number is straightforward and nice !
Why wouldarr.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 usea.name.toLowerCase() andb.name.toLowerCase()
45

You can useEasiest 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']);
turivishal's user avatar
turivishal
36.4k7 gold badges48 silver badges69 bronze badges
answeredAug 23, 2018 at 14:39
Harshal Yeole's user avatar

1 Comment

Here, this does not pass negative numbers.
45

Lodash (a superset ofUnderscore.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 morefunctional 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 shorthanduser.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 usingchain?

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

Or when do you preferflow over chain?

const { flow, reverse, sortBy } = require('lodash/fp');const sortedObjs = flow([sortBy('user.age'), reverse])(users);
Peter Mortensen's user avatar
Peter Mortensen
31.4k22 gold badges110 silver badges134 bronze badges
answeredAug 30, 2017 at 14:15
Nico Van Belle's user avatar

Comments

42

I haven't seen this particular approach suggested, so here's a terse comparison method I like to use that works for bothstring andnumber 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 ofsortBy()

sortBy() accepts afn that selects a value from an object to use in comparison, and returns a function that can be passed toArray.prototype.sort(). In this example, we're comparingo.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 waylogical 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 thethree-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));

answeredJul 18, 2018 at 9:53
Patrick Roberts's user avatar

4 Comments

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 anif statement, would be much more readable whilst still being fairly concise. I think it's a mistake to sacrifice readability for prettiness.
@MSOACC thanks for your opinion but I respectfully disagree. Other languages implement athree-way comparison operator that performs the same comparison, so just think of it conceptually asfa <=> fb.
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 thanks for the feedback. I'm aware that it sorts based on the code point of the character in unicode. However changing it to uselocaleCompare removes the ability to sort numbers, and is alsosignificantly slower.
36

Instead of using a custom comparison function, you could also create an object type with customtoString() 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();
answeredJul 15, 2009 at 7:21
Christoph's user avatar

Comments

36

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) )

Wherefn1,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 thefirst evaluated expression which can be converted to true.

The simplest form has only one inlined function like this:

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

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

// ORDER_BY last_nom, first_nomobjs.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_nomobjs.sort((a,b)=> cmp(a,b, "last_nom") || cmp(a,b, "first_nom") )// ORDER_BY last_nom, first_nom DESCobjs.sort((a,b)=> cmp(a,b, "last_nom") || -cmp(a,b, "first_nom") )// ORDER_BY last_nom DESC, first_nom DESCobjs.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.

Peter Mortensen's user avatar
Peter Mortensen
31.4k22 gold badges110 silver badges134 bronze badges
answeredMay 5, 2016 at 11:36
Tero Tolonen's user avatar

Comments

33

Try this:

Up toES5
// Ascending sortitems.sort(function (a, b) {   return a.value - b.value;});// Descending sortitems.sort(function (a, b) {   return b.value - a.value;});
InES6 and above
// Ascending sortitems.sort((a, b) => a.value - b.value);// Descending sortitems.sort((a, b) => b.value - a.value);
Peter Mortensen's user avatar
Peter Mortensen
31.4k22 gold badges110 silver badges134 bronze badges
answeredMar 3, 2020 at 8:50
Abhishek's user avatar

3 Comments

best and easy solution
Doesn't work for me, tried other solutions that does indeed work but this one doesn't. Attempted to sort by strings.
Does not correctly pass negative numbers
30

Use JavaScriptsort method

Thesort 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 anoptional argument to the sort method.

This compare function accepts 2 arguments generally calleda andb. 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 thesort() method sortsa at a lower index thanb. Simply a will come before b.
  2. If the compare function returns equal to 0, then thesort() method leaves the element positions as they are.
  3. If the compare function returns greater than 0, then thesort() method sortsa at greater index thanb. Simplya will come afterb.

Use the above concept to apply on your object wherea 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

Martijn Pieters's user avatar
Martijn Pieters
1.1m326 gold badges4.2k silver badges3.4k bronze badges
answeredFeb 3, 2021 at 8:13
Satish Chandra Gupta's user avatar

Comments

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;  };};
answeredApr 30, 2014 at 10:02
Jamie Mason's user avatar

3 Comments

thank you for breaking this down, I am trying to understand why digits1, 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 digits1, 0, -1 are used to determine order? Thanks.
This isn'tentirely 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.
thank you for taking the time to explain this further. So using your explanation and theMDN Array.prototype.sort, I'll tell you what I'm thinking of this: in comparison toa andb, ifa is greater thanb add 1 to the index ofa and place it behindb, ifa is less thanb, subtract 1 froma and place it in front ofb. Ifa andb are the same, add 0 toa and leave it where it is.
27

Write short code:

objs.sort((a, b) => a.last_nom > b.last_nom ? 1 : -1)
Peter Mortensen's user avatar
Peter Mortensen
31.4k22 gold badges110 silver badges134 bronze badges
answeredApr 9, 2021 at 12:58
artem's user avatar

7 Comments

what if values are equal? considering there are 3 values you can return -1, -1, 0
@SomeoneSpecial so what? The result will be the same
What does 1 || -1 mean ?
@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.
There is no bit mask, expressiona>b && 1|| -1 is equal toa> b ? 1 : -1, operator&& returns first logicalfalse value, operator|| returns first logicaltrue value.
|
25

I didn't see any implementation similar to mine. This version is based on theSchwartzian 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 attributeconsole.log(sortByAttribute(games, 'name'));// Sort by mupltiple attributesconsole.log(sortByAttribute(games, '-rating', 'name'));
Peter Mortensen's user avatar
Peter Mortensen
31.4k22 gold badges110 silver badges134 bronze badges
answeredNov 6, 2016 at 13:18
a8m's user avatar

Comments

21

Sorting objects withIntl.Collator for the specific case when you wantnatural 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: theundefined constructor argument forIntl.Collator represents the locale, which can be an explicit ISO 639-1 language code such asen, or the system default locale whenundefined.

Browser support forIntl.Collator

answeredFeb 24, 2022 at 19:00
ccpizza's user avatar

Comments

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 lovelyanswer.

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 byPeople.Name.name and secondarily byPeople.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 likePeople['Name.name'] would work - which doesn't.

Simply doingPeople['Name']['name'], on the other hand, is static and only allows you to go down then-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 exampleon JSBin

answeredAug 10, 2015 at 15:52
eljefedelrodeodeljefe's user avatar

1 Comment

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) })
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 datavar 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 TestsarrObj.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");

Not A Robot's user avatar
Not A Robot
2,7002 gold badges20 silver badges37 bronze badges
answeredApr 23, 2013 at 16:07
Mike R's user avatar

1 Comment

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).
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.

Peter Mortensen's user avatar
Peter Mortensen
31.4k22 gold badges110 silver badges134 bronze badges
answeredJun 26, 2016 at 9:10
ravshansbox's user avatar

3 Comments

Slightly changed version for sorting by multiple fields is here if needed:stackoverflow.com/questions/6913512/…
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; }; }
agreed, but in some case i haven't got needs to look at utility functions.
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.

answeredJan 15, 2016 at 13:32
Caio Ladislau's user avatar

3 Comments

You could usearrow functions to let the code a little more elegant:objs.sort( (a,b) => b.last_nom.toLowerCase() < a.last_nom.toLowerCase() );
This is wrong for the same reason as explainedhere.
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)
13

Warning!
Using this solution isnot 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})
Ben Carp's user avatar
Ben Carp
26.9k11 gold badges70 silver badges83 bronze badges
answeredMar 8, 2016 at 9:51
Roshni Bokade's user avatar

1 Comment

Actually it didn't seem to work, had to use the accepted answer. It wasn't sorting correctly.
13

This is my take on this:

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

It works onaccented characters and it's caseinsensitive.

Note: It sorts and returns theoriginal 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));

Peter Mortensen's user avatar
Peter Mortensen
31.4k22 gold badges110 silver badges134 bronze badges
answeredDec 20, 2019 at 9:49
cbdeveloper's user avatar

2 Comments

not working if the list contain name in the combination of uppercase and lowercase character
@AnkeshPandey Thanks for pointing that out. I've fixed it.
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) isuniversally supported and returns -1,0,1 ifa<b,a==b,a>b respectively.
  • || in the last line giveslast_nom priority overfirst_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;
answeredFeb 24, 2018 at 0:54
Bob Stein's user avatar

Comments

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"); // AscsortArray(objs, "last_nom", -1); // Desc
Peter Mortensen's user avatar
Peter Mortensen
31.4k22 gold badges110 silver badges134 bronze badges
answeredMay 28, 2018 at 19:54
Francois Girard's user avatar

1 Comment

This solution worked perfectly for me to sort bi-directional. Thank u
12

Additional desc parameters forEge Ö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;    }}
Peter Mortensen's user avatar
Peter Mortensen
31.4k22 gold badges110 silver badges134 bronze badges
answeredSep 16, 2012 at 20:22
Behnam Shomali's user avatar

1 Comment

What is "desc" for?"descending"?"descriptor"? Something else?
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})
Peter Mortensen's user avatar
Peter Mortensen
31.4k22 gold badges110 silver badges134 bronze badges
answeredAug 14, 2013 at 10:40
Burak Keceli's user avatar

Comments

Protected question. To answer this question, you need to have at least 10 reputation on this site (not counting theassociation bonus). The reputation requirement helps protect this question from spam and non-answer activity.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.