I want to sort an array of objects by multiple fields (3-4 in my case). Some values of object could be undefined and those values must be always "earlier" in sorting (ascending and descending). In my case everything works when sorting is descending, but in case of ascending values which are undefined are always in the bottom. Type of those values is always a string and I could call only .sort() method nothing before it and after. My code looks like this:
[{id: "1", label: "Z", code: "Z"},
{id: "1", code: "A"},
{id: "2", label: "A",},
{id: "3", label: "A", code: "A"}]
.sort((a,b) => {
return a.id.localeCompare(b.id)
|| a.label.localeCompare(b.label)
|| a.code.localeCompare(b.code)
);
Just write your own comparison function. 0 means they're the same, a positive number means b should come first, a negative number means a should come first.
const localeCompareUndefined = (a, b, locales, options) => {
if (a === undefined && b === undefined) return 0;
else if (b === undefined) return 1;
else if (a === undefined) return -1;
else return a.localeCompare(b, locales, options);
}
const data = [{id: "1", label: "Z", code: "Z"},
{id: "1", code: "A"},
{id: "2", label: "A",},
{id: "3", label: "A", code: "A"}]
.sort((a, b) => {
return localeCompareUndefined(a.id, b.id) ||
localeCompareUndefined(a.label, b.label) ||
localeCompareUndefined(a.code, b.code)
});
console.log(data);
Related
when we need to compare two objects a and b we also should test that one of them is not null.
However, knowing that is a total chaos
{} - null => -0
[] - null => 0
1 - null => 1
"1" - null => 1
true - null => 1
false - null => 0
"a" - null => NaN
null - null => 0
"a" == null false
"a" > null false
"a" < null false
let arr = [
{ name: "a" },
{ name: null },
null,
{ name: "zaa" },
{ name: "dgh" }
];
let sortByName = function (a, b) {
if (a == null || b == null) return a - b;
if (a.name == null || b.name == null) return a.name - b.name;
return a.name.localeCompare(b.name);
};
console.log(arr.sort(sortByName));
the result is the following:
0: {name: 'a'}
1: {name: null}
2: null
3: {name: 'dgh'}
4: {name: 'zaa'}
how would you explain such a result?
null - {} === NaN
{} - null === -0
Here:
if (a == null || b == null) return a - b;
You are subtracting anything from null or null from anything, if one of the 2 values is null.
Replace null with an empty string and use that in your comparison:
let arr = [
{ name: "a" },
{ name: null },
null,
{ name: "zaa" },
{ name: "dgh" }
];
let sortByName = function (a, b) {
return (a?.name ?? '').localeCompare(b?.name ?? '');
};
console.log(arr.sort(sortByName));
If you want to make it so that null comes before {name: null} you can extend #Cerbrus answer to handle this.
A simple way to do this is convert both terms a and b into values that will in the end sort the way you like. I find the easiest way is to just prefix the input terms with another character you want to sort on.
eg.
If term is null, just return `0`;
If term is `{name:null}` return `1`;
Everything else just return `3` + term;
It's kind of like creating a compound index..
Doing the above you can see it's very easy to make the sort do whatever you want based on certain conditions.. Eg, if you wanted null to go to the end like undefined simply change it's prefix, eg. make it 4
eg.
let arr = [
{ name: "a" },
{ name: null },
null,
{ name: "zaa" },
{ name: "dgh" }
];
function toStr(a) {
if (a === null) return '0';
else if (a.name === null) return '1';
else return '3' + a.name;
}
let sortByName = function (a, b) {
return toStr(a).localeCompare(toStr(b));
};
console.log(arr.sort(sortByName));
I want to update the first array's value on the basis of the second array.
For Example. This is my first Array
let a = [{id:"1",name:"none"},{id:"2",name:"two"}]
This is my second Array
let b = [{id:"1",name:"one"}]
This is what I want as an output.
console.log(a) //[{id:"1",name:"one"},{id:"2",name:"two"}]
Currently, I'm trying to achieve this by using array.filters and array.foreach but I'm getting an empty array.
a = a.filter((item) => {
b.forEach(i => {
if(item.id == i.id){
return i
}else{
return item
}
})
})
Ps. I don't want to create a new Array, I just want to update a's value on the basis of b. Thanks!
This is Test Case 2.
Array 1
let x = [
{id:"1", status:"occupied"},
{id:"2", status:"occupied"},
{id:"3", status:"occupied"},
{id:"4", status:"occupied"},
{id:"5", status:"occupied"},
{id:"6", status:"occupied"},
{id:"7", status:"occupied"}
]
Array 2
let z = [
{id:"1",status:"cleaning"},
{id:"3",status:"cleaning"},
{id:"5",status:"cleaning"},
{id:"7",status:"cleaning"},
{id:"2",status:"cleaning"},
]
Object.assign
let y = Object.assign(x,z);
console.log(y);//
I'm getting this output which is messed up.
{id: "1", status: "cleaning"}
{id: "3", status: "cleaning"}
{id: "5", status: "cleaning"}
{id: "7", status: "cleaning"}
{id: "2", status: "cleaning"}
{id: "6", status: "occupied"}
{id: "7", status: "occupied"}
You can use Object.assign which merges two objects by taking care of duplicates`
let c = Object.assign(a,b);
console.log(c);
How about something like this:
const a = [{id:"1",name:"none"},{id:"2",name:"two"}];
const b = [{id:"1",name:"one"}];
for (const item of a) {
const bFound = b.find(bItem => bItem.id === item.id);
if (bFound) {
item.name = bFound.name;
}
}
console.log(a);
try this:
let a = [{id:"1",name:"none"},{id:"2",name:"two"}]
let b = [{id:"1",name:"one"}]
var merge = (a, b, p) => a.filter( aa => ! b.find ( bb => aa[p] === bb[p]) ).concat(b)
console.log(merge(a, b, "id"))
I am building a REST API in NodeJS. I am building the server-side pagination, sorting and filtering.
I have a simple implementation of the filter method. This works if the item does not contain any empty strings. However if the item contains an empty string, the .toString() method fails because item[filterBy] is null.
Cannot read property 'toString' of null
How can I update the filter function to simply return true if the item contains an empty string, to skip the comparison operations?
// Apply filter to array of objects
// TODO: Empty strings breaks the filter
items = items.filter(item =>
item[filterBy].toString().trim().toLowerCase().indexOf(filterValue.toLowerCase()) !== -1)
To skip the item contains an empty string, try this:
item[filterBy] && item[filterBy].toString()
So, your code will be:
items = items.filter(item =>
item[filterBy] && item[filterBy].toString().trim().toLowerCase().indexOf(filterValue.toLowerCase()) !== -1)
Perhaps something like this:
const filterValue = 'abc'
const filterBy = 'name'
const items = [
{x: 1, name: 'abc'},
{x: 2, name: 'def'},
{x: 3, noname: 'def'},
{x: 4, name: 'ABCDEF'},
]
const newItems = items.filter(item =>
!(filterBy in item) || item[filterBy].toString().trim().toLowerCase().indexOf(filterValue.toLowerCase()) !== -1)
console.log(newItems)
All we do here is check if the item has a filterBy property.
It's not quite clear to me what the issue is with empty strings. It seems most likely that you would get that error if item[filterBy] is undefined. That's what we check here.
If instead you want to skip those that don't have the relevant property, switch from !(filterBy in item) || ... to (filterBy in item) && ....
Maybe:
items = items.filter(
item => item[filterBy] && item[filterBy].toString ?
item[filterBy].toString().trim().toLowerCase().indexOf(filterValue.toLowerCase()) !== -1 :
false
)
flatMap() is .map() and .flat() combined. It can act like a reverse .filter() by directly returning or not returning values. See demo for details.
const items = [{
id: 'a',
pn: 'B',
desc: ''
}, {
id: 'b',
pn: 'c',
desc: 'd'
}, {
id: 'c',
pn: 'k',
desc: null
}, {
id: 'i',
pn: 'k',
desc: 2
},, {
id: 'o',
pn: 'x',
desc: 3
}];
// Utility function that returns a 'friendlier' value
function clean(obj) {
return obj == null ? false : Array.isArray(obj) ? obj : obj.constructor.name.toLowerCase() === "object" ? obj.toString().trim().toLowerCase() : typeof obj === "string" ? obj.toString().trim().toLowerCase() : Number(parseFloat(obj)) === obj ? obj : false;
}
// Filters by a single key and multiple values
let filterKey = clean('desc');
let filterVal = clean(['d', 2, 'f']);
/* It returns each value in a sub-array an empty array will remove
the value. In the final stage it flattens the array of arrays into a normal array
*/
let list = items.flatMap(item => {
return Object.entries(item).flatMap(([key, value]) => filterKey === (clean(key)) && [...filterVal].includes(clean(value)) ? [clean(value)] :[]);
});
console.log(list);
It is correct to chain filters and it does not affect performance.
const filterValue = 'John'
const filterBy = 'name'
const items = [
{id: 1, name: 'John'},
{id: 2, name: 'Doe, John'},
{id: 3, ref: 1},
{id: 4, name: 'No one'},
{id: 5, name: null}
]
let fItems = items.filter(item => item[filterBy])
.filter(item => item[filterBy].toString().trim().toLowerCase()
.indexOf(filterValue.toLowerCase()) !== -1)
console.log(fItems);
Update: fixed the code.
I am attempting to sort an array of objects by a name property that exists on each object. When using the sort() method with the code below I am getting the following error:
ERROR ReferenceError: b is not defined
Here is my code:
myArray.sort( (a, b) => {
return (typeof a.name: string === 'string') - (typeof b.name === 'string')|| a.name - b.name || a.name.localeCompare(b.name)};
Here is what is odd though...
When I run:
myArray.sort( (a, b) => {
console.log(a.name);
console.log(b.name);
It logs the names perfectly fine. What am I missing??
Just to be a thorough little bit of context:
I am using this method after doing an HTTP call from an angular service.ts file and this array is being passed to my component and subscribed to. And I am using Angular, so this would be Typescript compiling to JavaScript. I also have another myArray.forEach() method just below my sort() method and that is working.
Is this what you want?
var a = [
{ name: "John" },
{ name: "Jack" },
{ name: "Bob" }
];
a.sort(function (a, b) {
if (a.name > b.name) return 1;
if (a.name < b.name) return -1;
return 0;
});
console.log(a);
You could use a comparison which works independently of the type of string or number, by moving numerical values to top.
var array = [{ name: 20 }, { name: 21 }, { name: 2 }, { name: 11 }, { name: 1 }, { name: 'John' }, { name: 'Jane' }, { name: 'Frank' }, { name: 'Mary' },]
array.sort((a, b) => (typeof a.name === 'string') - (typeof b.name === 'string') || a.name > b.name || -(a.name < b.name));
console.log(array);
I have a local storage that looks like this:
Key: Savedme
Value:
{
"Bob":["1","1"],
"John":["2","1"],
"Mom":["3","1"],
"Dad":["1","2"],
"Tom":["3","2"],
"Skipper42":["2","3"],
"Hated_41":["3","3"],
"Greeneggs":["2","2"],
"William":["1","3"]
}
I need to somehow sort it to look like this
{
"Bob":["1","1"],
"Dad":["1","2"],
"William":["1","3"]
"John":["2","1"],
"Greeneggs":["2","2"],
"Skipper42":["2","3"],
"Mom":["3","1"],
"Tom":["3","2"],
"Hated_41":["3","3"]
}
I've tried storing it in a matrix such as this:
var $runthrough = [[]];
$runthrough[$x,$y] = $values;
Where x is the first set of numbers, y is the next and then values is Bob, Dad etc...from there I could just do a foreach for both sections of the matrix and it would be done, HOWEVER when I use this method after it runs through one set of the objects, the second set gives an "undefined" even though I have setup some triggers to check and it's not actually going undefined.
var loadarray = JSON.parse(localStorage.getItem( 'savedme' ));
$.each(loadarray, function(k, v) {
if(typeof k === 'undefined' || !k){
console.error("undefined found at k!");
};
if(typeof v[0] === 'undefined' || !v[0]){
console.error("undefined found at x!");
};
if(typeof v[1] === 'undefined' || !v[1]){
console.error("undefined found at y!");
};
});
so I've come to realize, I'm probably doing something wrong with arrays so I figured it would be faster to sort out the array and THEN use the same function. It HAS to be ordered like this because it's basically going to be outputted to a matrix table, I tried ordering it like this:
{
"1":["1","Bob"],
"2":["1","John"],
}
but...the index value 1 would just be overwritten by the last value
You could chain the deltas of values with the same indices.
var a = [
{ "Bob": ["1", "1"] },
{ "John": ["2", "1"] },
{ "Mom": ["3", "1"] },
{ "Dad": ["1", "2"] },
{ "Tom": ["3", "2"] },
{ "Skipper42": ["2", "3"] },
{ "Hated_41": ["3", "3"] },
{ "Greeneggs": ["2", "2"] },
{ "William": ["1", "3"] }
];
a.sort(function (a, b) {
var aa = a[Object.keys(a)],
bb = b[Object.keys(b)];
if (aa[0] === bb[0]) {
return aa[1] - bb[1];
} else {
return aa[0] - bb[0];
}
});
document.querySelector("#demo").innerHTML = JSON.stringify(a, null, 4);
<div id="demo"></div>
Update 2021
const
array = [{ Bob: ["1", "1"] }, { John: ["2", "1"] }, { Mom: ["3", "1"] }, { Dad: ["1", "2"] }, { Tom: ["3", "2"] }, { Skipper42: ["2", "3"] }, { Hated_41: ["3", "3"] }, { Greeneggs: ["2", "2"] }, { William: ["1", "3"] }];
array.sort((a, b) => {
const
aa = Object.values(a)[0],
bb = Object.values(b)[0];
return aa[0] - bb[0] || aa[1] - bb[1];
});
console.log(array);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Objects properties do not have a guaranteed order in JavaScript, you need to use an Array.
Definition of an Object from ECMAScript Third Edition (pdf):
4.3.3 Object
An object is a member of the
type Object. It is an unordered collection of properties each of which
contains a primitive value, object, or
function. A function stored in a
property of an object is called a
method.
Try a data structure like this instead:
[
{ name: "Bob", value: ["1","1"] },
{ name: "Dad", value: ["1","2"] },
{ name: "William", value: ["1","3"] },
{ name: "John", value: ["2","1"] },
{ name: "Greeneggs", value: ["2","2"] },
{ name: "Skipper42", value: ["2","3"] },
{ name: "Mom", value: ["3","1"] },
{ name: "Tom", value: ["3","2"] },
{ name: "Hated_41", value: ["3","3"] }
]
You can generate this structure like this:
var loadarray = JSON.parse(localStorage.getItem( 'savedme' ));
var sorted = [];
for (var prop in loadarray) {
if (loadarray.hasOwnProperty(prop)) {
sorted.push({name:prop, value:loadarray[prop]});
}
}
sorted.sort(function(a, b) {
var v0 = a.value[0] - b.value[0];
return v0 == 0 ? a.value[0] - a.value[0] : v0;
});
Keys cannot be sorted within an object.
However, they can be processed in order using Object.keys(object).sort().
Here, I'm outputting the object to an array – sorted by the keys' values – then displaying that array:
var obj= {
"Bob":["1","1"],
"John":["2","1"],
"Mom":["3","1"],
"Dad":["1","2"],
"Tom":["3","2"],
"Skipper42":["2","3"],
"Hated_41":["3","3"],
"Greeneggs":["2","2"],
"William":["1","3"]
}
var arr= Object.keys(obj)
.sort(function(a, b) {
if(obj[a][0]===obj[b][0]) {
return obj[a][1] - obj[b][1];
}
else {
return obj[a][0] - obj[b][0];
}
})
.map(function(key) {
var o= {};
o[key]= obj[key];
return o;
});
document.body.innerHTML= JSON.stringify(arr);
Very late, but you can try this, assuming if you have an array:
var data=[{Value:
{
"Bob":["1","1"],
"John":["2","1"],
"Mom":["3","1"],
"Dad":["1","2"],
"Tom":["3","2"],
"Skipper42":["2","3"],
"Hated_41":["3","3"],
"Greeneggs":["2","2"],
"William":["1","3"]
}}];
data.forEach((elem)=>{
for(const property in elem.Value){
var k = elem.Value[property];
k.sort((a,b)=>a-b);
}
});
console.log(data);