Sort array of objects by string property value

Asked 2023-09-20 20:13:13 View 256,593

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?

  • Case sensitive or case insensitive sort? - anyone

Answers

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

Answered   2023-09-20 20:13:13

  • Or inline: objs.sort(function(a,b) {return (a.last_nom > b.last_nom) ? 1 : ((b.last_nom > a.last_nom) ? -1 : 0);} ); - anyone
  • Official docs: developer.mozilla.org/en/JavaScript/Reference/Global_Objects/… - anyone
  • return a.last_nom.localeCompare(b.last_nom) will work, too. - anyone
  • for those looking for a sort where the field is numeric, the compare function body: return a.value - b.value; (ASC) - anyone
  • 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. - anyone

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

Answered   2023-09-20 20:13:13

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

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

Answered   2023-09-20 20:13:14

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

Answered   2023-09-20 20:13:14

  • 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. - anyone
  • To reverse sort: var reverseSortedObjs = _.sortBy( objs, 'first_nom' ).reverse(); - anyone
  • 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> - anyone
  • Also available in Lodash for the ones who prefer that one - anyone
  • 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'] ); - anyone

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

Answered   2023-09-20 20:13:14

  • The case sensitive approach is a good shorthand - especially if the values are numeric or date. - anyone
  • TIP: if you'd like to reverse the order, you can simply swap -1 and 1 for eg: from 1 : -1 to -1 : 1 - anyone
  • What about changing (a, b) to (b, a) :) - anyone
  • Yes, that works too. I just find swapping 1 & -1 more straight forward and logical. - anyone
  • 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. - anyone

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

Answered   2023-09-20 20:13:14

  • @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. - anyone
  • @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/… - anyone
  • @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. - anyone

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

Answered   2023-09-20 20:13:14

  • 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. - anyone
  • 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. - anyone
  • 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. - anyone

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.

Answered   2023-09-20 20:13:14

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

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.

Answered   2023-09-20 20:13:14

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

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

Answered   2023-09-20 20:13:14

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']);

Answered   2023-09-20 20:13:14

  • Here, this does not pass negative numbers. - anyone

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

Answered   2023-09-20 20:13:14

  • 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. - anyone
  • @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. - anyone
  • 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 ;-) - anyone
  • @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. - anyone

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

Answered   2023-09-20 20:13:14

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.

Answered   2023-09-20 20:13:14

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

Answered   2023-09-20 20:13:14

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

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

Answered   2023-09-20 20:13:14

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;
  };
};

Answered   2023-09-20 20:13:14

  • 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. - anyone
  • 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. - anyone
  • 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. - anyone

Write short code:

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

Answered   2023-09-20 20:13:14

  • what if values are equal? considering there are 3 values you can return - 1, -1, 0 - anyone
  • @SomeoneSpecial so what? The result will be the same - anyone
  • What does 1 || -1 mean ? - anyone
  • @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. - anyone
  • 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. - anyone

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

Answered   2023-09-20 20:13:14

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

Answered   2023-09-20 20:13:14

  • 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) }) - anyone

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

Answered   2023-09-20 20:13:14

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

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.

Answered   2023-09-20 20:13:14

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

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.

Answered   2023-09-20 20:13:14

  • 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() ); - anyone
  • This is wrong for the same reason as explained here. - anyone
  • 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) - anyone

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

Answered   2023-09-20 20:13:14

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

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

Answered   2023-09-20 20:13:14

  • not working if the list contain name in the combination of uppercase and lowercase character - anyone
  • @AnkeshPandey Thanks for pointing that out. I've fixed it. - anyone

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;

Answered   2023-09-20 20:13:14

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

Answered   2023-09-20 20:13:14

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

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;
    }
}

Answered   2023-09-20 20:13:14

  • What is "desc" for? "descending"? "descriptor"? Something else? - anyone

Sorting objects with Intl.Collator for the specific case when you want natural 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

Answered   2023-09-20 20:13:14

Using Ramda,

npm install ramda

import R from 'ramda'
var objs = [ 
    { first_nom: 'Lazslo', last_nom: 'Jamf'     },
    { first_nom: 'Pig',    last_nom: 'Bodine'   },
    { first_nom: 'Pirate', last_nom: 'Prentice' }
];
var ascendingSortedObjs = R.sortBy(R.prop('last_nom'), objs)
var descendingSortedObjs = R.reverse(ascendingSortedObjs)

Answered   2023-09-20 20:13:14

  • What is Ramda? Can you add a reference to it (e.g., a (non-naked) link)? (But ******* without ******* "Edit:", "Update:", or similar - the answer should appear as if it was written today). - anyone