I have an array of objects that I am trying to sort but it does not seem to be working. Some objects in the array have a orderNum property which I am targeting to sort. But not all objects have this property.
I want the objects with the orderNum property to be sorted to the top positions in the array.
Here is a fiddle to what i have tried: http://jsfiddle.net/7D8sN/
Here is my javascript:
var data = {
"attributes": [
{
"value": "123-456-7890",
"name": "phone"
},
{
"value": "Something#something.com",
"name": "email"
},
{
"value": "Gotham",
"name": "city",
"orderNum": 1
},
{
"value": "12",
"name": "ID"
},
{
"value": "Batman",
"name": "Super Hero",
"orderNum": 2
}
]
};
data.attributes.sort( function (a, b) {
if (a.orderNum < b.orderNum) {
return -1;
}
if (a.orderNum > b.orderNum) {
return 1;
}
return 0;
});
console.log(data);
Check if the property exists in your sort function.
data.attributes.sort( function (a, b) {
if ((typeof b.orderNum === 'undefined' && typeof a.orderNum !== 'undefined') || a.orderNum < b.orderNum) {
return -1;
}
if ((typeof a.orderNum === 'undefined' && typeof b.orderNum !== 'undefined') || a.orderNum > b.orderNum) {
return 1;
}
return 0;
});
You have to check specifically for the property being undefined. Otherwise, both tests return false, so you fall through to return 0 and treat them as equal to everything.
data.attributes.sort( function (a, b) {
if (a.orderNum === undefined || a.orderNum < b.orderNum) {
return -1;
}
if (b.orderNum === undefined || b.orderNum < a.orderNum) {
return 1;
}
return 0;
});
You can check whether each object has the property with hasOwnProperty("orderNum") and then sort them accordingly. If one has it, and the other does not, then the one with it gets put first. I made the assumption that you were sorting with orderNum ascending.
JSFiddle: http://jsfiddle.net/dshell/RFr5N/
data.attributes.sort( function (a, b) {
if ((a.hasOwnProperty("orderNum")) && (b.hasOwnProperty("orderNum")))
{
return a.orderNum - b.orderNum;
}
else if (a.hasOwnProperty("orderNum"))
{
return -1;
}
else if (b.hasOwnProperty("orderNum"))
{
return 1;
}
return 0;
});
What you need is to 'normalize' your input :
data.attributes.sort( function (a, b) {
var aOrderNum = ( a.orderNum === undefined ) ? -1 : a.orderNum ;
var bOrderNum = ( b.orderNum === undefined ) ? -1 : b.orderNum ;
return aOrderNum - bOderNum;
});
Related
I have a v-data-table in vueJS that contains some numeric columns and some string columns.
In each column, some of the values are null.
I am trying to create a custom sorting function that will place null values last.
This is what I tried so far:
<v-data-table
:headers="[
{ text: 'Name', value: 'name' },
{ text: 'Date of Birth', value: 'dateofbirth_fmt' },
{ text: 'Team', value: 'team_name' },
{
text: 'dp1 (string)',
value: 'dp1',
},
{
text: 'dp2 (Numeric),
value: 'dp2',
}
]"
:items="filteredPlayersData"
item-key="_id"
class="elevation-1"
:custom-sort="customSort"
/>
and this function
customSort(items, index, isDesc) {
items.sort((a, b) => {
if (!isDesc[0]) {
return (a[index] != null ? a[index] : Infinity) >
(b[index] != null ? b[index] : Infinity)
? 1
: -1;
} else {
return (b[index] != null ? b[index] : -Infinity) >
(a[index] != null ? a[index] : -Infinity)
? 1
: -1;
}
});
return items;
}
It is working for this numeric column (dp1), but not for the string one (dp2).
Any ideas how to get this work?
Your sorting algorithm is not working correctly for strings.
Imagine that your first string is null, and the second one is 'Jelly bean'.
Instead of null value you are trying to compate Infinity with 'Jelly bean'.
This comparison will be false in both cases:
let a = Infinity;
let b = 'Jelly bean';
console.log(a > b);
console.log(a < b);
It'd be better to use another sorting algorithm.
For example, I've adapted an algorithm from this post:
customSort(items, index, isDesc) {
items.sort((a, b) => {
if (a[index] === b[index]) { // equal items sort equally
return 0;
} else if (a[index] === null) { // nulls sort after anything else
return 1;
} else if (b[index] === null) {
return -1;
} else if (!isDesc[0]) { // otherwise, if we're ascending, lowest sorts first
return a[index] < b[index] ? -1 : 1;
} else { // if descending, highest sorts first
return a[index] < b[index] ? 1 : -1;
}
});
return items;
}
You may test this at CodePen. Works fine for both strings and numbers.
I have an array of objects. For eg-
[{
aKey:2,
bKey:2,
cKey:3
}, {
bKey:2,
cKey:6
}, {
aKey:1,
bKey:6,
cKey:5
}, {
bKey:1,
cKey:4
}, {
bKey:6,
cKey:7
}]
So what I need to do is-
First sort the array on the basis of aKey (asc order) and the objects which are having this key would be at the beginning in result array.
Then I need to sort the array based on the value of bKey. for eg, all the records having bKey = 2, would be at the beginning.
Rest records would be sorted based on the value of cKey in asc order.
So the output will be-
[{
aKey:1,
bKey:6,
cKey:5
}, {
aKey:2,
bKey:2,
cKey:3
}, {
bKey:2,
cKey:6
}, {
bKey:1,
cKey:4
}, {
bKey:6,
cKey:7
}]
For sorting preferentially from aKey to bKey and then to cKey, you can use this:
var array=[{aKey:2,bKey:2,cKey:3},{bKey:2,cKey:6},{aKey:1,bKey:6,cKey:5},{bKey:1,cKey:4},{bKey:6,cKey:7}]
var result = array.sort(function(hash) {
return function(a, b) {
return ((a.aKey || Infinity) - (b.aKey || Infinity))
|| ((a.bKey || Infinity) - (b.bKey || Infinity))
|| ((a.cKey || Infinity) - (b.cKey || Infinity))
}
}(Object.create(null)));
console.log(result);
.as-console-wrapper{top:0;max-height:100%!important;}
But you want bKey:2 to come before bKey:1 as for the last element
that has aKey, the value of bKey is 2.
To adjust for this anomaly, without knowing which element is to follow once aKey is done with (and extending to the case where bKey is also done with too), you can do this - hash these anomaly keys and sort accordingly - see demo below:
var array=[{aKey:2,bKey:2,cKey:3},{aKey:1,bKey:6,cKey:5},{bKey:1,cKey:4},{bKey:6,cKey:7},{bKey:2,cKey:7},{bKey:2,cKey:6},{cKey:4},{cKey:7}]
var result = array.sort(function(hash) {
return function(a, b) {
// find the anomaly keys
a.aKey && !b.aKey && (hash.bkey = a.bKey);
a.bKey && !b.bKey && (hash.ckey = a.cKey);
// sort criteria
return ((a.aKey || Infinity) - (b.aKey || Infinity))
|| (((a.bKey != hash.bkey) - (b.bKey != hash.bkey)) || ((a.bKey || Infinity) - (b.bKey || Infinity)))
|| (((a.cKey != hash.ckey) - (b.cKey != hash.ckey)) || ((a.cKey || Infinity) - (b.cKey || Infinity)))
}
}(Object.create(null)));
console.log(result);
.as-console-wrapper{top:0;max-height:100%!important;}
You can use sort() like this
var data = [{
aKey:2,
bKey:2,
cKey:3
}, {
bKey:2,
cKey:6
}, {
aKey:1,
bKey:6,
cKey:5
}, {
cKey:41
}, {
cKey:7
}, {
bKey:1,
cKey:4
}, {
bKey:6,
cKey:7
}]
data.sort(function(a, b) {
return ((b.aKey != undefined) - (a.aKey != undefined) || a.aKey - b.aKey) ||
((b.bKey != undefined) - (a.bKey != undefined) || ((a.bKey != 2) - (b.bKey != 2)) || a.bKey - b.bKey) ||
((b.cKey != undefined) - (a.cKey != undefined) || a.cKey - b.cKey)
})
console.log(data)
I have the following structure:
var participant1 = {
name : "bbb",
nickname : "",
} ;
var participant2 = {
name : "",
nickname : "aaa"
} ;
var participant3 = {
name : "ccc",
nickname : ""
} ;
And i have an array which contain instances of structure :
var array = [participant3, participant1, participant2];
I would like to sort this array by alphabetical letter. First on name, but if name doesn't exist, i would like to use nickname to sort. The final result will be :
var array = [participant2, participant1, participant3];
(To have sorted object by "aaa", "bbb", "ccc")
Use this custom sortfunction
var arr = [participant3, participant1, participant2],
sortFunc = function(a,b){
if(a.name !== '' && b.name !== ''){
return a.name > b.name;
} else if(a.name !== ''){
return a.name > b.nickname;
} else if(b.name !== ''){
return a.nickname > b.name;
} else{
return a.nickname > b.nickname;
}
}
arr.sort(sortFunc);
You need to create a compare function and then use .sort()
function compare(a,b) {
if (a.name< b.name)
return -1;
else if (a.name> b.name)
return 1;
else
{
if (a.nickname< b.nickname) return -1;
else return 1;
}
}
array.sort(compare);
I have following array from facebook graph API.
I want to sort it by comment_count, like_count, time in javascript.
[
{
"status_id": "1",
"message": "message1",
"comment_info": {
"comment_count": "1"
},
"like_info": {
"like_count": "0"
},
"time": "1380046653"
},
{
"status_id": "2",
"message": "message2",
"comment_info": {
"comment_count": "2"
},
"like_info": {
"like_count": "5"
},
"time": "1368109884"
}
]
I wrote function like below,
function sortResults(prop, asc) {
statusString = statusString.sort(function(a, b) {
if (asc) return (a[prop] > b[prop]) ? 1 : ((a[prop] < b[prop]) ? -1 : 0);
else return (b[prop] > a[prop]) ? 1 : ((b[prop] < a[prop]) ? -1 : 0);
});
console.log(statusString);
}
And on button click
sortResults(['comment_info']['comment_count'], true);
But it sorts weiredly.
Your function didnt' take in consideration multi dimensional sort which needs to access deep property of nested arrays.
Here is a working example JSFIDLE link (click here)
var jSon = [{"status_id":"1","message":"message1","comment_info":{"comment_count":"1"},"like_info":{"like_count":"0"},"time":"1380046653"},{"status_id":"2","message":"message2","comment_info":{"comment_count":"2"},"like_info":{"like_count":"5"},"time":"1368109884"}];
// Function that sorts arr Array
// by prop (handling custom Fb cases)
// in dir direction (asc/desc)
function sortJson(arr, prop, dir) {
return arr.sort(function(a,b) {
var propA,propB;
if (prop == "comment_count") {
propA = a['comment_info']['comment_count'];
propB = b['comment_info']['comment_count'];
} else if (prop == "like_count") {
propA = a['like_info']['like_count'];
propB = b['like_info']['like_count'];
} else {
propA = a[prop];
propB = b[prop];
}
if (dir=='asc') {
return propA - propB;
} else {
return propB - propA;
}
});
}
console.log( sortJson(jSon, 'time', 'asc') );
console.log( sortJson(jSon, 'comment_count', 'asc') );
console.log( sortJson(jSon, 'like_count', 'desc').toString() );
You probably need sort function from native [].
[].sort(compareFunction)
example from here :
function compare(a, b) {
if (a is less than b by some ordering criterion)
return -1;
if (a is greater than b by the ordering criterion)
return 1;
// a must be equal to b
return 0;
}
let's say your order in the sort is as you have mentioned them:
var arr = [{"status_id":"1","message":"message1","comment_info":{"comment_count":"1"},"like_info":{"like_count":"0"},"time":"1380046653"},{"status_id":"2","message":"message2","comment_info":{"comment_count":"2"},"like_info":{"like_count":"5"},"time":"1368109884"}];
arr.sort(function (a, b) {
var countA = parseInt(a["comment_info"]["comment_count"]);
var countB = parseInt(b["comment_info"]["comment_count"]);
var likeCountA = parseInt(a["like_info"]["like_count"]);
var likeCountB = parseInt(b["like_info"]["like_count"]);
var timeA = a["time"];
var timeB = b["time"];
return ((countA - countB) || (likeCountA - likeCountB) || (timeA - timeB));
});
I have this function to sort a JavaScript array of objects based on a property:
// arr is the array of objects, prop is the property to sort by
var sort = function (prop, arr) {
arr.sort(function (a, b) {
if (a[prop] < b[prop]) {
return -1;
} else if (a[prop] > b[prop]) {
return 1;
} else {
return 0;
}
});
};
It works with arrays like this:
sort('property', [
{property:'1'},
{property:'3'},
{property:'2'},
{property:'4'},
]);
But I want to be able to sort also by nested properties, for example something like:
sort('nestedobj.property', [
{nestedobj:{property:'1'}},
{nestedobj:{property:'3'}},
{nestedobj:{property:'2'}},
{nestedobj:{property:'4'}}
]);
However this doesn't work because it is not possible to do something like object['nestedobj.property'], it should be object['nestedobj']['property'].
Do you know how could I solve this problem and make my function work with properties of nested objects?
Thanks in advance
You can split the prop on ., and iterate over the Array updating the a and b with the next nested property during each iteration.
Example: http://jsfiddle.net/x8KD6/1/
var sort = function (prop, arr) {
prop = prop.split('.');
var len = prop.length;
arr.sort(function (a, b) {
var i = 0;
while( i < len ) { a = a[prop[i]]; b = b[prop[i]]; i++; }
if (a < b) {
return -1;
} else if (a > b) {
return 1;
} else {
return 0;
}
});
return arr;
};
Use Array.prototype.sort() with a custom compare function to do the descending sort first:
champions.sort(function(a, b) { return b.level - a.level }).slice(...
Even nicer with ES6:
champions.sort((a, b) => b.level - a.level).slice(...
Instead of passing the property as a string, pass a function that can retrieve the property from the top level object.
var sort = function (propertyRetriever, arr) {
arr.sort(function (a, b) {
var valueA = propertyRetriever(a);
var valueB = propertyRetriever(b);
if (valueA < valueB) {
return -1;
} else if (valueA > valueB) {
return 1;
} else {
return 0;
}
});
};
Invoke as,
var simplePropertyRetriever = function(obj) {
return obj.property;
};
sort(simplePropertyRetriever, { .. });
Or using a nested object,
var nestedPropertyRetriever = function(obj) {
return obj.nestedObj.property;
};
sort(nestedPropertyRetriever, { .. });
if you have array of objects like
const objs = [{
first_nom: 'Lazslo',
last_nom: 'Jamf',
moreDetails: {
age: 20
}
}, {
first_nom: 'Pig',
last_nom: 'Bodine',
moreDetails: {
age: 21
}
}, {
first_nom: 'Pirate',
last_nom: 'Prentice',
moreDetails: {
age: 22
}
}];
you can use simply
nestedSort = (prop1, prop2 = null, direction = 'asc') => (e1, e2) => {
const a = prop2 ? e1[prop1][prop2] : e1[prop1],
b = prop2 ? e2[prop1][prop2] : e2[prop1],
sortOrder = direction === "asc" ? 1 : -1
return (a < b) ? -sortOrder : (a > b) ? sortOrder : 0;
}
and call it
for direct objects
objs.sort(nestedSort("last_nom"));
objs.sort(nestedSort("last_nom", null, "desc"));
for nested objects
objs.sort(nestedSort("moreDetails", "age"));
objs.sort(nestedSort("moreDetails", "age", "desc"));
You can use Agile.js for this kind of things.
Actually you pass an expression instead of callback, it's handle nested properties and javascript expression in a very nice-ish way.
Usage: _.orderBy(array, expression/callback, reverse[optional])
Example:
var orders = [
{ product: { price: 91.12, id: 1 }, date: new Date('01/01/2014') },
{ product: { price: 79.21, id: 2 }, date: new Date('01/01/2014') },
{ product: { price: 99.90, id: 3 }, date: new Date('01/01/2013') },
{ product: { price: 19.99, id: 4 }, date: new Date('01/01/1970') }
];
_.orderBy(orders, 'product.price');
// → [orders[3], orders[1], orders[0], orders[2]]
_.orderBy(orders, '-product.price');
// → [orders[2], orders[0], orders[1], orders[3]]
Would this meet your needs?
// arr is the array of objects, prop is the property to sort by
var sort = function (nestedObj, prop, arr) {
arr.sort(function (a, b) {
if (a[nestedObj][prop] < b[nestedObj][prop]) {
return -1;
} else if (a[nestedObj][prop] > b[nestedObj][prop]) {
return 1;
} else {
return 0;
}
});
};
Try this (used a recursive function to get nested value, you can pass the nested property as nestedobj.property):
You can use this for any level of hierarchy
// arr is the array of objects, prop is the property to sort by
var getProperty = function(obj, propNested){
if(!obj || !propNested){
return null;
}
else if(propNested.length == 1) {
var key = propNested[0];
return obj[key];
}
else {
var newObj = propNested.shift();
return getProperty(obj[newObj], propNested);
}
};
var sort = function (prop, arr) {
arr.sort(function (a, b) {
var aProp = getProperty(a, prop.split("."));
var bProp = getProperty(a, prop.split("."));
if (aProp < bProp) {
return -1;
} else if (aProp > bProp) {
return 1;
} else {
return 0;
}
});
};
This is my modify code.
// arr is the array of objects, prop is the property to sort by
var s = function (prop, arr) {
// add sub function for get value from obj (1/2)
var _getVal = function(o, key){
var v = o;
var k = key.split(".");
for(var i in k){
v = v[k[i]];
}
return v;
}
return arr.sort(function (a, b) {
// get value from obj a, b before sort (2/2)
var aVal = _getVal(a, prop);
var bVal = _getVal(b, prop);
if (aVal < bVal) {
return -1;
} else if (aVal > bVal) {
return 1;
} else {
return 0;
}
});
};
var objectsArr = [
{nestedobj:{property:'1'}},
{nestedobj:{property:'3'}},
{nestedobj:{property:'2'}},
{nestedobj:{property:'4'}}
];
function getFromPath(obj, path) {
let r = obj;
path.forEach(key => { r = r[key]})
return r
}
function sortObjectsArr(objectsArray, ...path) {
objectsArray.sort((a, b) => getFromPath(a, path) - getFromPath(b, path))
}
sortObjectsArr(objectsArr, 'nestedobj', 'property');
console.log(objectsArr);
Unfortunately, I didn't find any nice way to use the arguments in order to access the attributes of the nested object.
Want to mention that there can be some checks if the keys are available in the passed object, but this depends on who and how want to implement this.
Description
My solution is this one. I decide to flat the object first:
function flattenObject(value: any): any {
let toReturn: any = {};
for (const i in value) {
if (!value.hasOwnProperty(i)) {
continue;
}
if (typeof value[i] == 'object') {
const flatObject = flattenObject(value[i]);
for (const x in flatObject) {
if (!flatObject.hasOwnProperty(x)) continue;
toReturn[i + '.' + x] = flatObject[x];
}
} else {
toReturn[i] = value[i];
}
}
return toReturn;
}
And then I'll extract the value from the object:
function nestedFieldValue(
nestedJoinedFieldByDot: string,
obj: any,
): any {
return flattenObject(obj)[nestedJoinedFieldByDot];
}
Ant at the end I just need to do this:
export function fieldSorter(fields: string[]) {
return function (a: any, b: any) {
return fields
.map(function (fieldKey) {
// README: Sort Ascending by default
let dir = 1;
if (fieldKey[0] === '-') {
// README: Sort Descending if `-` was passed at the beginning of the field name
dir = -1;
fieldKey = fieldKey.substring(1);
}
const aValue = nestedFlattenObjectFieldValue(
fieldKey,
a,
);
const bValue = nestedFlattenObjectFieldValue(
fieldKey,
b,
);
if (
typeof aValue === 'number' ||
typeof bValue === 'number'
) {
/**
* README: default value when the field does not exists to prevent unsorted array
* I assume that 0 should be the last element. In other word I sort arrays in a way
* that biggest numbers comes first and then smallest numbers
*/
if (aValue ?? 0 > bValue ?? 0) {
return dir;
}
if (aValue ?? 0 < bValue ?? 0) {
return -dir;
}
} else {
if (aValue ?? 0 > bValue ?? 0) {
return dir;
}
if (aValue ?? 0 < bValue ?? 0) {
return -dir;
}
}
return 0;
})
.reduce(function firstNonZeroValue(p, n) {
return p ? p : n;
}, 0);
};
}
Finally we need to do this:
const unsorted = [
{
city: {
priority: 1,
name: 'Tokyo',
airport: { name: 'Haneda Airport' }
}
}
]
const result = unsorted.sort(
fieldSorter(['city.priority', 'city.airport.name', 'city.name']),
);
I think this way is much much clear and cleaner. It is readable and more functional. I merge multiple answer from stackoverflow to reach this solution :sweat_smile:
This should work and can accept multiple parameters.
https://codepen.io/allenACR/pen/VwQKWZG
function sortArrayOfObjects(items, getter) {
const copy = JSON.parse(JSON.stringify(items));
const sortFn = fn => {
copy.sort((a, b) => {
a = fn(a)
b = fn(b)
return a === b ? 0 : a < b ? -1 : 1;
});
};
getter.forEach(x => {
const fn = typeof x === 'function' ? x : item => item[x];
sortFn(fn);
});
return copy;
}
// example dataset
const data = [
{id: 3, name: "Dave", details: {skill: "leader"} },
{id: 1, name: "Razor", details: {skill: "music"} },
{id: 2, name: "Syd", details: {skill: "animal husbandry"} }
]
// sort via single prop
const sort1 = sortArrayOfObjects(data, ["id"])
// returns [Razor, Syd, Dave]
// sort via nested
const sort2 = sortArrayOfObjects(data, [
(item) => item.details.skill
])
// returns [Syd, Dave, Razor]
console.log({sort1, sort2})
3 levels deep. path can look like this.
'level1' or 'level1.level2' or 'level1.level2.level3'
I also did uppercase for the sort all my items are strings.
Anwser is a modified version from - #Mas
public keysrt(arr: Object[], path: string, reverse: boolean): void {
const nextOrder = reverse ? -1 : 1;
const pathSplit = path.split('.');
if (arr === null || arr === undefined ) {
return;
}
if (arr.length <= 1) {
return;
}
const nestedSort = (prop1, prop2 = null, prop3 = null, direction = 'asc') => (e1, e2) => {
const a = prop3 ? e1[prop1][prop2][prop3] : prop2 ? e1[prop1][prop2] : e1[prop1],
b = prop3 ? e2[prop1][prop2][prop3] : prop2 ? e2[prop1][prop2] : e2[prop1],
sortOrder = direction === 'asc' ? 1 : -1;
return (a.toString().toUpperCase() < b.toString().toUpperCase()) ?
-sortOrder * nextOrder : (a.toString().toUpperCase() > b.toString().toUpperCase()) ?
sortOrder * nextOrder : 0;
};
if (pathSplit.length === 3) {
arr.sort(nestedSort(pathSplit[0], pathSplit[1], pathSplit[2]));
}
if (pathSplit.length === 2) {
arr.sort(nestedSort(pathSplit[0], pathSplit[1]));
}
if (pathSplit.length === 1) {
arr.sort(nestedSort(pathSplit[0], null));
}
}
For those who a researching on how to sort nested properties. I created a small type-safe array sorting method with support for deeply nested properties and Typescript autocompletion.
https://github.com/jvandenaardweg/sort-by-property
https://www.npmjs.com/package/sort-by-property
Example:
blogPosts.sort(sortByProperty('author.name', 'asc'));