How one can write a function, which takes only few attributes in most-compact way in ES6?
I've came up with solution using destructuring + simplified object literal, but I don't like that list of fields is repeated in the code.
Is there an even slimmer solution?
(v) => {
let { id, title } = v;
return { id, title };
}
Here's something slimmer, although it doesn't avoid repeating the list of fields. It uses "parameter destructuring" to avoid the need for the v parameter.
({id, title}) => ({id, title})
(See a runnable example in this other answer).
#EthanBrown's solution is more general. Here is a more idiomatic version of it which uses Object.assign, and computed properties (the [p] part):
function pick(o, ...props) {
return Object.assign({}, ...props.map(prop => ({[prop]: o[prop]})));
}
If we want to preserve the properties' attributes, such as configurable and getters and setters, while also omitting non-enumerable properties, then:
function pick(o, ...props) {
var has = p => o.propertyIsEnumerable(p),
get = p => Object.getOwnPropertyDescriptor(o, p);
return Object.defineProperties({},
Object.assign({}, ...props
.filter(prop => has(prop))
.map(prop => ({prop: get(props)})))
);
}
I don't think there's any way to make it much more compact than your answer (or torazburo's), but essentially what you're trying to do is emulate Underscore's pick operation. It would be easy enough to re-implement that in ES6:
function pick(o, ...fields) {
return fields.reduce((a, x) => {
if(o.hasOwnProperty(x)) a[x] = o[x];
return a;
}, {});
}
Then you have a handy re-usable function:
var stuff = { name: 'Thing', color: 'blue', age: 17 };
var picked = pick(stuff, 'name', 'age');
The trick to solving this as a one-liner is to flip the approach taken: Instead of starting from original object orig, one can start from the keys they want to extract.
Using Array#reduce one can then store each needed key on the empty object which is passed in as the initialValue for said function.
Like so:
const orig = {
id: 123456789,
name: 'test',
description: '…',
url: 'https://…',
};
const filtered = ['id', 'name'].reduce((result, key) => { result[key] = orig[key]; return result; }, {});
console.log(filtered); // Object {id: 123456789, name: "test"}
alternatively...
const filtered = ['id', 'name'].reduce((result, key) => ({
...result,
[key]: orig[key]
}), {});
console.log(filtered); // Object {id: 123456789, name: "test"}
A tiny bit shorter solution using the comma operator:
const pick = (O, ...K) => K.reduce((o, k) => (o[k]=O[k], o), {})
console.log(
pick({ name: 'John', age: 29, height: 198 }, 'name', 'age')
)
ES6 was the latest spec at the time when the question was written. As explained in this answer, key picking is significantly shorter in ES2019 than in ES6:
Object.fromEntries(
Object.entries(obj)
.filter(([key]) => ['foo', 'bar'].includes(key))
)
TC39's object rest/spread properties proposal will make this pretty slick:
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
z; // { a: 3, b: 4 }
(It does have the downside of creating the x and y variables which you may not need.)
You can use object destructuring to unpack properties from the existing object and assign them to variables with different names - fields of a new, initially empty object.
const person = {
fname: 'tom',
lname: 'jerry',
aage: 100,
}
let newPerson = {};
({fname: newPerson.fname, lname: newPerson.lname} = person);
console.log(newPerson);
There's currently a strawman proposal for improving JavaScript's object shorthand syntax, which would enable "picking" of named properties without repetition:
const source = {id: "68646", genre: "crime", title: "Scarface"};
const target = {};
Object.assign(target, {source.title, source.id});
console.log(picked);
// {id: "68646", title: "Scarface"}
Unfortunately, the proposal doesn't seem to be going anywhere any time soon. Last edited in July 2017 and still a draft at Stage 0, suggesting the author may have ditched or forgotten about it.
ES5 and earlier (non-strict mode)
The concisest possible shorthand I can think of involves an ancient language feature nobody uses anymore:
Object.assign(target, {...(o => {
with(o) return { id, title };
})(source)});
with statements are forbidden in strict mode, making this approach useless for 99.999% of modern JavaScript. Bit of a shame, because this is the only halfway-decent use I've found for the with feature. 😀
I have similar to Ethan Brown's solution, but even shorter - pick function. Another function pick2 is a bit longer (and slower), but allows to rename properties in the similar to ES6 manner.
const pick = (o, ...props) => props.reduce((r, p) => p in o ? {...r, [p]: o[p]} : r, {})
const pick2 = (o, ...props) => props.reduce((r, expr) => {
const [p, np] = expr.split(":").map( e => e.trim() )
return p in o ? {...r, [np || p]: o[p]} : r
}, {})
Here is the usage example:
const d = { a: "1", c: "2" }
console.log(pick(d, "a", "b", "c")) // -> { a: "1", c: "2" }
console.log(pick2(d, "a: x", "b: y", "c")) // -> { x: "1", c: "2" }
I required this sollution but I didn't knew if the proposed keys were available. So, I took #torazaburo answer and improved for my use case:
function pick(o, ...props) {
return Object.assign({}, ...props.map(prop => {
if (o[prop]) return {[prop]: o[prop]};
}));
}
// Example:
var person = { name: 'John', age: 29 };
var myObj = pick(person, 'name', 'sex'); // { name: 'John' }
Some great solutions above, didn't see one for Typescript fleshed out, so here it goes. Based on #Ethan Browns solution above
const pick = < T extends object, K extends keyof T >(
obj: T,
...keys: K[]
): Pick< T, K > =>
keys.reduce< any >( ( r, key ) => {
r[ key ] = obj[ key ];
return r;
}, {} );
And for bonus, here is TS friendly es6 omit, and one that is much more performant below, but less es6.
const omit = < T extends object, K extends keyof T >(
obj: T,
...keys: K[]
): Omit< T, K > =>
keys.reduce( ( r, key ) => ( delete r[ key ], r ), {
...obj,
} );
Way more performant omit: http://jsben.ch/g6QCK
const omit = < T extends object, K extends keyof T >(
obj: T,
...keys: K[]
): Omit< T, K > => {
let r: any = {};
let length = keys.length;
while ( length-- ) {
const key = keys[ length ];
r[ key ] = obj[ key ];
}
return r;
};
inspired by the reduce approach of https://stackoverflow.com/users/865693/shesek:
const pick = (orig, keys) => keys.reduce((acc, key) => ({...acc, [key]: orig[key]}), {})
or even slightly shorter using the comma operator (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comma_Operator)
const pick = (obj, keys) => keys.reduce((acc, key) => ((acc[key] = obj[key]), acc), {});
usage:
pick({ model : 'F40', manufacturer: 'Ferrari', productionYear: 1987 }, 'model', 'productionYear')
results in:
{model: "F40", productionYear: 1987}
I have an array of JavaScript objects:
var objs = [
{ first_nom: 'Lazslo', 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?
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
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");
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)
});
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');
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);
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;
});
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;
});
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.
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.
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);
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']);
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));
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();
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.
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);
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.
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.
If the compare function returns equal to 0, then the sort() method leaves the element positions as they are.
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)
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;
};
};
Write short code:
objs.sort((a, b) => a.last_nom > b.last_nom ? 1 : -1)
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'));
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
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");
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.
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.
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})
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));
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
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;
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;
}
}
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)
function compare(propName) {
return function(a,b) {
if (a[propName] < b[propName])
return -1;
if (a[propName] > b[propName])
return 1;
return 0;
};
}
objs.sort(compare("last_nom"));