Sort by Object within Object in JavaScript - javascript

I have a persons list and I sort it by the column in sortColumn.
const persons = [{
name: "alireza",
family: "seif",
other: {
age: 28,
rate: 30
}
},
{
name: "sara",
family: "niki",
other: {
age: 15,
rate: 15
}
},
{
name: "fateme",
family: "azizy",
other: {
age: 27,
rate: 35
}
}
];
const sortColumn = {
path: "name",
order: "asc"
};
persons.sort((person1, person2) =>
person1[sortColumn.path] > person2[sortColumn.path] ?
sortColumn.order === "asc" ?
1 :
-1 :
person2[sortColumn.path] > person1[sortColumn.path] ?
sortColumn.order === "asc" ?
-1 :
1 :
0
);
console.log(persons);
If sortColumn.path is "name" and order is "asc" (or "desc"), the sort function works correctly. But how can I sort by "other.age"?
Thanks.

You could take a functin which return a sort function, depending on the sort order.
This sorting function uses another function for getting a value from an object by reducing the splitted path to the value.
const
sortBy = ({ order = 'asc', path }) => order === 'asc'
? (a, b) => ((a, b) => a > b || -(a < b))(getValue(a, path), getValue(b, path))
: (a, b) => ((a, b) => b > a || -(b < a))(getValue(a, path), getValue(b, path)),
getValue = (object, keys) => keys.split('.').reduce((o, k) => o[k], object),
persons = [{ name: "sara", family: "niki", other: { age: 15, rate: 15 } }, { name: "alireza", family: "seif", other: { age: 28, rate: 30 } }, { name: "fateme", family: "azizy", other: { age: 27, rate: 35 } }];
console.log(persons.sort(sortBy({ path: "name", order: "asc" })));
console.log(persons.sort(sortBy({ path: "other.rate", order: "desc" })));
.as-console-wrapper { max-height: 100% !important; top: 0; }

If you want to do sort by that deep property age, you can't use sortColumn exactly as you have... Instead, one option is to modify it by making it an array of properties, like so:
sortColumn = { path:["other","age"], order:"asc" }
This way, you'd also have to modify the sort function - as seen in the example below:
const persons = [
{name:"alireza",family:"seif",other:{age:28,rate:30}},
{name:"fateme",family:"azizy",other:{age:27,rate:35}},
{name:"sara",family:"niki",other:{age:15,rate:15}}
]
const sortColumn = { path:["other","age"], order:"asc" }
persons.sort((person1, person2) =>
person1[sortColumn.path[0]][sortColumn.path[1]] > person2[sortColumn.path[0]][sortColumn.path[1]]
? sortColumn.order === "asc"
? 1
: -1
: person2[sortColumn.path[0]][sortColumn.path[1]] > person1[sortColumn.path[0]][sortColumn.path[1]]
? sortColumn.order === "asc"
? -1
: 1
: 0
);
console.log(persons)
However, this approach doesn't work to sort by "name" since this sort function sorts your data by some piece of data that is two-layers deep (inside "other", then "age"). Here's a modification you can make to the sort function which lets you sort by any properties, any number of layers deep into your data:
const persons = [
{name:"alireza",family:"seif",other:{age:28,rate:30}},
{name:"fateme",family:"azizy",other:{age:27,rate:35}},
{name:"sara",family:"niki",other:{age:15,rate:15}}
]
const sortColumn = { path:["name"], order:"asc" }
persons.sort((person1, person2) => {
// get array of paths
const sortPaths = sortColumn.path;
// get values to sort by
const val1 = sortPaths.reduce((acc, path) => {
if (acc[path]) return acc[path]
else alert(`can't find prop ${path} in person1`);
}, person1)
const val2 = sortPaths.reduce((acc, path) => {
if (acc[path]) return acc[path]
else alert(`can't find prop ${path} in person2`);
}, person2)
return val1 > val2
? sortColumn.order === "asc"
? 1
: -1
: val2 > val1
? sortColumn.order === "asc"
? -1
: 1
: 0
});
console.log(persons)
https://stackoverflow.com/questions/59792589/sort-object-in-object-by-javascript/59792918#
As you can see in this second snippet, you can now search by a shallow property "name" by using path: ["name"], but if you want to sort by a deep value, just add both properties to the path array like this: path: ["other", "age"]
Hope this helps!

The main thing to do is to make your code expect 'path' be a function that selects a property's value, instead of a string pointing to a property name. That way it's much more flexible.
const sortColumn = {
path: p => p.other.age,
order: 'desc'
};
However, if 'persons' is not the only object you'd like to do this with, you can further abstract such a function for general use with any array, such as this:
function sorter (array, path, order) {
array.sort((a,b) => {
let result =
path(a) > path(b) ? 1
: path(a) < path(b) ? -1
: 0;
if (order === "desc")
result = -result;
return result;
});
}
Use it like this:
sorter(persons, p => p.other.age, 'desc');
Expand and run the snippet below to see it in action:
function sorter (array, path, order) {
array.sort((a,b) => {
let result =
path(a) > path(b) ? 1
: path(a) < path(b) ? -1
: 0;
if (order === "desc")
result = -result;
return result;
});
}
const persons = [
{name: "alireza", family: "seif", other: {age: 28, rate: 30}},
{name: "sara", family: "niki", other: {age: 15, rate: 15}},
{name: "fateme", family: "azizy", other: {age: 27, rate: 35}}
];
// convenience function
let sortAndLog = (array, path, order) => {
sorter(array, path, order);
console.log(array.map(path));
}
sortAndLog(persons, p => p.name, "asc");
sortAndLog(persons, p => p.name, "desc");
sortAndLog(persons, p => p.other.age, "asc");
sortAndLog(persons, p => p.other.age, "desc");

Related

Sort only few objects from an array of object coming from api response in React

I am looking for an efficient way of sorting the API response which is array of Objects. This Array has many fields and I just want to sort only few of them.
The Array looks like this
result = {type: Array(), status: Array(), nature: Array(), health: Array(), fitness: Array(), wealth: Array()}
and Array have name and value property like {name:"", value:""}
so let's say I just need to sort type, status, and nature out of this result. The thing that I have tried now looks like this which juts sorts one of the records.
const typeName = "type"
if(result[typeName]){
result[typeName] = sortingFunction(result[typeName], "name")
}
Now I need to sort other fields as well and also for few fields I need to sort on the basis of "value" property as well.
So please let me know if you have any efficient way of doing this.
You could create a sort function which can sort the given input object for the given keys.
I have create a sample function for sorting.
This function has two parameters.
First the object which needs to be sorted
Second option, you can pass the option for sort.
a. sortBy: Name of the property on which the function will perform the sort .
b. sortKeys: Array | String, the keys/key of the object which need to be sorted.
Function:
function sortObject(input, options = {}) {
if (!options)
return;
let keys = options.sortKeys;
let sortBy = options.sortby
if (!sortBy) {
console.error("sort by option is not defiend");
return;
}
if (!keys) {
console.error("sort keys are not defiend");
return;
}
if (Array.isArray(keys) && keys.length > 0) {
keys.forEach(item => sortObjectByKey(item, sortBy));
return;
}
if (typeof keys === "string" && keys) {
sortObjectByKey(keys, sortBy);
return;
}
function sortObjectByKey(sortKey, sortBy) {
input[sortKey].sort(function (a, b) {
let _a = (typeof a[sortBy] === "string") ? a[sortBy].toLowerCase() : a[sortBy];
let _b = (typeof b[sortBy] === "string") ? b[sortBy].toLowerCase() : b[sortBy];
if (_a < _b)
return -1
if (_a > _b)
return 1
return 0
});
}
}
Example:
//sortObject(sampleObject, { sortby: ["name", "value"], sortKeys: ["status", "type"] });
function sortObject(input, options = {}) {
if (!options)
return;
let keys = options.sortKeys;
let sortBy = options.sortby
if (!sortBy) {
console.error("sort by option is not defiend");
return;
}
if (!keys) {
console.error("sort keys are not defiend");
return;
}
if (Array.isArray(keys) && keys.length > 0) {
keys.forEach(item => sortObjectByKey(item, sortBy));
return;
}
if (typeof keys === "string" && keys) {
sortObjectByKey(keys, sortBy);
return;
}
function sortObjectByKey(sortKey, sortBy) {
input[sortKey].sort(function (a, b) {
let _a = (typeof a[sortBy] === "string") ? a[sortBy].toLowerCase() : a[sortBy];
let _b = (typeof b[sortBy] === "string") ? b[sortBy].toLowerCase() : b[sortBy];
if (_a < _b)
return -1
if (_a > _b)
return 1
return 0
});
}
}
let sampleObject = {
type: [
{ name: "c", value: 4 },
{ name: "a", value: 2 },
{ name: "b", value: 1 },
{ name: "d", value: 3 },
],
status: [
{ name: "c", value: 25 },
{ name: "a", value: 25 },
{ name: "b", value: 25 },
{ name: "d", value: 25 },
],
nature: [
{ name: "c", value: 25 },
{ name: "a", value: 25 },
{ name: "b", value: 25 },
{ name: "d", value: 25 },
],
}
sortObject(sampleObject, { sortby: "value", sortKeys: ["type"] });
sortObject(sampleObject, { sortby: "name", sortKeys: ["status", "nature"] });
console.log(sampleObject)
One way is to translate the object of arrays into an array of objects, then merge it back after sorting.
const result = {
type: ['foo', 'bar', 'baz'],
status: [4, 3, 5],
nature: ['forest', 'animal', 'water'],
health: ['athlete', 'couch potato', 'dead'],
fitness: [200, 50, 60],
wealth: [5, 2, 99]
};
// 1. combine
const combined = result.type.map((_, index) => {
return Object.fromEntries(Object.keys(result).map(key => [key, result[key][index]]));
});
// 2. example sort by status
combined.sort((a, b) => a.status - b.status)
// 3. merge
combined.forEach((object, index) => {
for (const [key, value] of Object.entries(object)) {
result[key][index] = value
}
})
console.log(result);

Sort an array of objects, but the first place is fixed

Here is an example an object with an array I want to sort:
{
first: 'Zangief',
second: 'Cammy'
names: [
{name: 'Dee Jay'},
{name: 'Zangief'},
{name: 'Dhalsim'}
{name: 'Chun-Li'},
{name: 'Blanka'},
{name: 'Cammy'}
]
}
I want to have Zangief fixed on the first place and Cammy on the second place, and the rest is alphabetically ordered.
expected result:
[
{name: 'Zangief'},
{name: 'Cammy'},
{name: 'Blanka'}
{name: 'Chun-Li'},
{name: 'Dee Jay'},
{name: 'Dhalsim'},
]
I know this sorts the names alphabetically:
obj.names.sort((a,b) => (a.name > b.name) ? 1 : ((b.name > a.name) ? -1 : 0));
and then I could find the two names and put them to the first two places, but is there a sorting function, what could do this while sorting?
You could probably just modify your function to be something like that:
obj.names.sort((a,b) => {
if (a.name === obj.first || (a.name === obj.second && b.name !== obj.first)){
return -1;
}
return (a.name > b.name) ? 1 : ((b.name > a.name) ? -1 : 0);
});
Here's my 2 cents. I don't think it would be wise to hard code the names of the special names. I would add an order to your array that would cause the sort to override it's default ordering. So, then you wouldn't need the two properties first and second. If you are not able to modify the original array, then perhaps one of the other answers is more appropriate.
let object = {
names: [
{ name: 'Dee Jay' },
{ name: 'Zangief', order: 1 },
{ name: 'Dhalsim' },
{ name: 'Chun-Li' },
{ name: 'Blanka' },
{ name: 'Cammy', order: 2 }
]
}
object.names.sort((a, b) => {
return (a.order || Number.MAX_SAFE_INTEGER) - (b.order || Number.MAX_SAFE_INTEGER)
|| a.name.localeCompare(b.name);
});
object.names.forEach(entry => console.log(entry.name));
Another possibility, even though I don't really like it would be to pre-process the array before the new sort. Something like this:
object.names.find( entry => entry.name === 'Zangief' ).order = 1;
object.names.find( entry => entry.name === 'Cammy' ).order = 2;
object.names.sort( /* new sort */ );
with the appropriate error checking added. Again, I don't like this but it's a possibility.
You could build an object with the order of known and unkown names and take the value for ordering.
If the value is the same, then sort by string.
var object = { first: 'Zangief', second: 'Cammy', names: [{ name: 'Dee Jay' }, { name: 'Zangief' }, { name: 'Dhalsim' }, { name: 'Chun-Li' }, { name: 'Blanka' }, { name: 'Cammy' }] },
order = Object.assign(
...['first', 'second', ''].map((k, i) => ({ [object[k]]: i + 1 }))
);
object.names.sort(({ name: a }, { name: b }) =>
(order[a] || order.undefined) - (order[b] || order.undefined) || a.localeCompare(b)
);
console.log(object.names);
.as-console-wrapper { max-height: 100% !important; top: 0; }
let obj = {
first: 'Zangief',
second: 'Cammy',
names: [
{name: 'Dee Jay'},
{name: 'Zangief'},
{name: 'Dhalsim'},
{name: 'Chun-Li'},
{name: 'Blanka'},
{name: 'Cammy'}
]
};
obj.names.sort((a,b) => {
// exit early, (or trigger error if it should never happen)
if (a.name === b.name) return 0;
// No matter what, 'Zangief' gets moved to the front.
if (a.name === obj.first) return -1;
if (b.name === obj.first) return 1;
// if no Zangief, 'Cammy' always moves forward.
if (a.name === obj.second) return -1;
if (b.name === obj.second) return 1;
// otherwise, normal alphabetical sorting
return (a.name > b.name) ? 1 : -1;
});
console.log(obj.names);
Alternatively, you can do a long "one-liner":
filter returns a new array with obj.first and obj.second removed.
sort then sorts that new array in place, according to the usual rules.
concat returns a new array appending this sorted array to [obj.first, obj.second], your "starting" array.
let obj = {
first: 'Zangief',
second: 'Cammy',
names: [
{name: 'Dee Jay'},
{name: 'Zangief'},
{name: 'Dhalsim'},
{name: 'Chun-Li'},
{name: 'Blanka'},
{name: 'Cammy'}
]
};
let sorted = [{name: obj.first}, {name: obj.second}]
.concat(obj.names.filter(item => (
((item.name !== obj.first) &&
(item.name !== obj.second))
)).sort((a, b) => (a.name > b.name)
? 1
: ((b.name > a.name) ? -1 : 0)
));
console.log(sorted);
// simplified data structure
const first = 'Zangief';
const second= 'Cammy';
const names = [
'Dee Jay',
'Zangief',
'Dhalsim',
'Chun-Li',
'Blanka',
'Cammy'
];
// filter/concat (version 2) does not alter original names array.
let sorted = [first, second]
.concat(names.filter(name => (
!((name == first) || (name == second))
)).sort());
console.log("new sorted array, (version 2) via filter/concat: \n", sorted);
// orig array untouched
console.log("original names array (untouched): \n", names);
// custom sort, (version 1) alters the original names array.
names.sort((a,b) => {
// 'Zangief' gets moved to the front.
if (a === first) return -1;
if (b === first || b === second) return 1;
// Othwerwise 'Cammy' moves forward.
if (a === second) return -1;
// if (b === second) return 1;
// all other strings: normal alphabetical sorting
return (a, b) => (a > b) ? 1 : ((b > a) ? -1 : 0)
});
console.log("names array, altered, after (version 1) sorting", names);

Javascript can't group objects with 2 values

I have this object data:
[ RowDataPacket {
id: 59,
steamid: '76561198220437096',
product_id: 23,
status: 1,
date: 2017-12-18T17:27:19.000Z,
message: null,
name: 'CS.MONEY',
amount: 100,
website: 'csgo500' },
RowDataPacket {
id: 60,
steamid: '76561198220437096',
product_id: 24,
status: 1,
date: 2017-12-18T17:27:19.000Z,
message: null,
name: 'CS.MONEY',
amount: 250,
website: 'csgo500' },
RowDataPacket {
id: 61,
steamid: '76561198220437096',
product_id: 23,
status: 1,
date: 2017-12-18T17:27:19.000Z,
message: null,
name: 'CS.MONEY',
amount: 100,
website: 'csgo500' },
RowDataPacket {
id: 62,
steamid: '76561198345348530',
product_id: 6,
status: 1,
date: 2017-12-18T20:05:55.000Z,
message: null,
name: 'wal gruche',
amount: 100,
website: 'csgoatse' }
Im trying to sort this data with steamid and website, i managed to sort this only by one value like this:
var groupedOrders = {};
row.forEach(function(item){
var list = groupedOrders[item.steamid];
if(list){
list.push(item);
} else{
groupedOrders[item.steamid] = [item];
}
});
My idea was to make two dimensional array but for some reason i cant do it like this:
var list = groupedOrders[item.steamid][item.website];
It throws me an error "Cant read property ... of undefined"
Now my code looks like this:
var groupedOrders = {};
row.forEach(function(item){
var list = groupedOrders[item.steamid][item.website];
if(list){
list.push(item);
} else{
groupedOrders[item.steamid][item.website] = [item];
}
});
Do you have any ideas how to fix this errors?
The problem is that var list = groupedOrders[item.steamid][item.website] is actually saying:
var temp = groupedOrders[item.steamid];
var list = temp[item.website];
There is no entry at groupedOrders[item.steamid] and so line one sets temp to undefined. The second line tries to index into undefined which is an error.
You would have to split the code out and essentially do the whole one-key grouping twice:
var outerList = groupedOrders[item.steamid];
if (!outerList)
outerList = groupedOrders[item.steamid] = {};
var innerList = outerList[item.website];
if (innerList)
innerList.push(item);
else
outerList[item.website] = [item];
(I have not tested this code but it is the right shape.)
The following works by creating a recursive groupBy grouping function for each of the fields supplied as an argument.
These dynamically created groupBy functions are then invoked one by one, passing the result between, starting with the supplied data.
Each groupBy function instance creates an object and adds properties to it corresponding to the key values for the field being grouped.
By calling these groupBy functions successively, we create a progressively more nested tree of objects, with groups at each successive level marked as being groups using a symbol.
The final result is a nest (a tree!) of objects, with keys corresponding to the field used for indexing at that level.
Finally, we flatten the nest and the final order is visible.
const flatten = o => Object.values(o).reduce((acc, c) => (Array.isArray(c) ? [...acc, ...c] : typeof c === 'object' ? [...acc, ...flatten(c)] : [...acc, c]), []);
const flow = (...fns) => data => fns.reduce((acc, c) => c(acc), data);
const GROUP = Symbol('group');
const asGroup = (result = []) => ((result[GROUP] = true), result);
const isGroup = o => o[GROUP];
const groupBy = field => (data, key) =>
data.reduce((acc, c) =>
((key = c[field]), (acc[key] ?
(acc[key].push(c), acc) :
((acc[key] = asGroup([c])), acc))), {});
const recurse = (test) => (transform) => o =>
test(o)
? transform(o)
: Object.entries(o).reduce(
(acc, [k, v]) => (test(v) ?
((acc[k] = transform(v)), acc) :
((acc[k] = recurse(test)(transform)(v)), acc)), {});
const group = (...fields) => flow(...fields.map(flow(groupBy, recurse(isGroup))), flatten);
const rows = asGroup([
{
id: 0,
steamid: '2',
website: 'a'
},
{
id: 1,
steamid: '2',
website: 'b'
},
{
id: 2,
steamid: '2',
website: 'a'
},
{
id: 3,
steamid: '1',
website: 'b'
},
{
id: 4,
steamid: '0',
website: 'b'
}
]);
console.log(JSON.stringify(group('steamid', 'website')(rows), null, 2));

Count elements that have the same value for a specific property and put the result in an array of objects

Using Array.reduce, I am trying to count the elements that have the same value for a specific property. I want to put the result in an array of objects containing a property for the value of the grouped by property and another one for the count. How can I do this easily in javascript ?
const CATEGORY = {
STRATEGY: 'STRATEGY',
CONTENT: 'CONTENT',
ADVERTISING: 'ADVERTISING',
MEASURMENT: 'MEASURMENT'
}
const lessons = [
{
title: 'ohoho',
category: CATEGORY.STRATEGY
}, {
title: 'hihihi',
category: CATEGORY.CONTENT
}, {
title: 'hello',
category: CATEGORY.CONTENT
}
]
let categoryLessonCount = lessons.reduce(function (acc, lesson) {
acc[lesson.category] ? acc[lesson.category]++ : acc[lesson.category] = 1
return acc
}, {})
console.log(categoryLessonCount[CATEGORY.STRATEGY])
console.log(categoryLessonCount[CATEGORY.CONTENT])
Actual categoryLessonCount value :
Object
{
STRATEGY: 1,
CONTENT: 2
}
Wanted categoryLessonCount value :
Array
[
{
title: 'STRATEGY',
count: 1
}, {
title: 'CONTENT',
count: 2
}
]
You already got the what you want just transform it into an array
const CATEGORY = {
STRATEGY: 'STRATEGY',
CONTENT: 'CONTENT',
ADVERTISING: 'ADVERTISING',
MEASURMENT: 'MEASURMENT'
}
const lessons = [{
title: 'ohoho',
category: CATEGORY.STRATEGY
}, {
title: 'hihihi',
category: CATEGORY.CONTENT
}, {
title: 'hello',
category: CATEGORY.CONTENT
}]
let count = lessons.reduce(function(acc, lesson) {
acc[lesson.category] ? acc[lesson.category] ++ : acc[lesson.category] = 1
return acc
}, {})
// transform count into what you want
let categoryLessonCount = [];
for (let cat in count) {
categoryLessonCount.push({
'title': cat,
'count': count[cat]
});
}
console.log(categoryLessonCount)
Something like this should work:
let categoryLessonCount = lessons.reduce(function(acc, lesson) {
let found = false
for (const item of acc) {
if (item.title === lesson.category) {
item.count++
found = true
}
}
if (!found) {
acc.push({
title: lesson.category,
count: 1
})
}
return acc
}, [])
Your main issue is that your accumulating an object but expecting an array (note the final argument to reduce).
Short solution using Object.keys and Array.prototype.map functions:
...
let categoryLessonCount = lessons.reduce(function (acc, lesson) {
acc[lesson.category] ? acc[lesson.category]++ : acc[lesson.category] = 1
return acc
}, {})
let counts = Object.keys(categoryLessonCount).map(
(k) => ({title: k, count: categoryLessonCount[k]})
)
console.log(counts);

Javascript multiple condition array filter

I need help putting together an array search that is based on multiple conditions. Furthermore, all the conditions are conditional, meaning I may or may not need to filter on those conditions. What I have:
Array of objects to filter:
var data = [{
"_id" : ObjectId("583f6e6d14c8042dd7c979e6"),
"transid" : 1,
"acct" : "acct1",
"transdate" : ISODate("2012-01-31T05:00:00.000Z"),
"category" : "category1",
"amount" : 103
},
{
"_id" : ObjectId("583f6e6d14c8042dd7c2132t6"),
"transid" : 2,
"acct" : "acct2",
"transdate" : ISODate("2012-01-31T05:00:00.000Z"),
"category" : "category2",
"amount" : 103
},
{
"_id" : ObjectId("583f6e6d14c8042dd7c2132t6"),
"transid" : 3,
"acct" : "acct2",
"transdate" : ISODate("2016-07-31T05:00:00.000Z"),
"category" : "category1",
"amount" : 103
},
{
"_id" : ObjectId("583f6e6d14c8042dd7c2132t6"),
"transid" : 4,
"acct" : "acct2",
"transdate" : ISODate("2012-01-31T05:00:00.000Z"),
"category" : "category2",
"amount" : 103
},
{
"_id" : ObjectId("583f6e6d14c8042dd7c2132t6"),
"transid" : 5,
"acct" : "acct2",
"transdate" : ISODate("2012-01-31T05:00:00.000Z"),
"category" : "category3",
"amount" : 103
},
{
"_id" : ObjectId("583f6e6d14c8042dd7c152g2"),
"transid" : 6,
"acct" : "acct3",
"transdate" : ISODate("2016-10-31T05:00:00.000Z"),
"category" : "category3",
"amount" : 103
}]
I am filtering the above array of objects based on another array of mixed elements. The elements represent the following search fields:
"searchstring": to search on all fields in the data array for any
matched text sequence
object with key values reprsenting account type and a true or false
for value indicating if it should be used to filter
startdate to filter transdate on
enddate to filter transdate
category name to filter category on
The array that has the search conditions looks like this (but if some of the fields are not necessary they will be set to undefined or just an empty string or array):
var filtercondition = {
"p",
{acct1:true,acct2:false,acct3:true...}
"2016-06-01",
"2016-11-30",
"category3"
}
What is the best way to accomplish this? What I've devised is a separate search for each element in the filter array, but this seems non optimal and very tedious. I'm open to a redesign of my setup...
// You wrote that it's an array, so changed the braces
var filtercondition = ["p",
{acct1:true,acct2:false,acct3:true...}
"2016-06-01",
"2016-11-30",
"category3"
];
var filtered = data.filter(o => {
if(filtercondition[0] && !o.category.includes(filtercondition[o])) { // checking just the category, but you can check if any of more fields contains the conditions
return false;
}
if(filtercondition[1]) {
for(var key in filtercondition[1]) {
if(filtercondition[1][key] === true && o.acct != key) {
return false;
}
}
}
if(filtercondition[2] && o.transdate < filtercondition[2]) {
return false;
}
if(filtercondition[3] && o.transdate > filtercondition[3]) {
return false;
}
if(filtercondition[4] && o.category !== filtercondition[4]) {
return false;
}
return true;
});
Two notes:
- changed the braces of filtercondition so that it is an array, however I would suggest to use an object instead.
- this {acct1:true,acct2:false,acct3:true...} sample doesn't make sense for me, since it suggests that the acct field should be acct1 and acct3 at the same time.
Create an array of functions, each function representing a condition.
Here's some sample code which demonstrates the approach...
var conditions = [];
// Dynamically build the list of conditions
if(startDateFilter) {
conditions.push(function(item) {
return item.transdate >= startDateFilter.startDate;
});
};
if(categoryFilter) {
conditions.push(function(item) {
return item.cateogry === categoryFilter.category;
});
};
// etc etc
Once you have an array of conditions, you can use Array.prototype.every to run each condition on an item.
var itemsMatchingCondition = data.filter(function(d) {
return conditions.every(function(c) {
return c(d);
});
});
Or, using the more compact arrow functions:
const itemsMatchingCondition = data.filter(d => conditions.every(c => c(d));
First, you'll want to use brackets for your array not curly braces:
var filtercondition = [
"p",
{acct1:true,acct2:false,acct3:true...},
"2016-06-01",
"2016-11-30",
"category3"
];
Then again, I don't think that an array is the best data type for that. Try an object like this:
var filtercondition = {
query: "p",
accounts: {acct1:true,acct2:false,acct3:true...},
date1: "2016-06-01",
date2: "2016-11-30",
category: "category3"
};
Then, try using Array.prototype.filter:
var filtered = data.filter(function(obj) {
for (var key in filtercondition) {
// if condition not met return false
}
return true;
});
I'd go with a bunch of small granular functions and compose them.
//only some utilities, from the top of my mind
var identity = v => v;
//string-related
var string = v => v == null? "": String(v);
var startsWith = needle => haystack => string(haystack).startsWith(needle);
var endsWith = needle => haystack => string(haystack).endsWith(needle);
var contains = needle => haystack => string(haystack).contains(needle);
//do sth with an object
var prop = key => obj => obj != null && prop in obj? obj[prop]: undefined;
var someProp = fn => obj => obj != null && Object.keys(obj).some(k => fn(k) );
var someValue = fn => obj => obj != null && Object.keys(obj).some(k => fn(obj[k]) );
//logic
var eq = b => a => a === b;
var not = fn => function(){ return !fn.apply(this, arguments) };
var and = (...funcs) => funcs.reduce((a, b) => function(){
return a.apply(this, arguments) && b.apply(this, arguments);
});
var or = (...funcs) => funcs.reduce((a, b) => function(){
return a.apply(this, arguments) || b.apply(this, arguments);
});
//composition
var compose = (...funcs) => funcs.reduce((a, b) => v => return a(b(v)));
var chain = (...funcs) => funcs.reduceRight((a, b) => v => return a(b(v)));
//and whatever else you want/need
//but stay granular, don't put too much logic into a single function
and an example composition:
var filterFn = and(
//some value contains "p"
someValue(contains("p")),
//and
chain(
//property "foo"
prop("foo"),
or(
//either contains "asdf"
contains("asdf"),
//or startsWith "123"
startsWith("123")
)
),
)
since I don't know how you build your filterconditions, I cannot tell you exactly how to parse them into such a composition, but you could compose them like this:
//start with something basic, so we don't ever have to check wether filterFn is null
var filterFn = identity;
//and extend/compose it depending on some conditions
if(/*hasQuery*/){
filterFn = and(
// previous filterFn(obj) && some value on obj contains `query`
filterFn,
someValue(contains(query)))
)
}
if(/*condition*/){
//extend filterFn
filterFn = or(
// (obj.foo === null) || previous filterFn(obj)
chain(prop("foo"), eq(null)),
filterFn
);
}
and so on
First, some points:
Your data object is invalid if you're going to use it in the browser. Probably the data comes from MongoDB, right? Your backend (data source) should have a method to encode it properly and remove ObjectID and ISODate references.
Your filtercondition is not a valid JavaScript object/JSON. Check my example.
So, you can filter your data array with Array#filter method.
Something like that:
let data = [{
"_id" : "583f6e6d14c8042dd7c979e6",
"transid" : 1,
"acct" : "acct1",
"transdate" : "2012-01-31T05:00:00.000Z",
"category" : "category1",
"amount" : 103
},
{
"_id" : "583f6e6d14c8042dd7c2132t6",
"transid" : 2,
"acct" : "acct2",
"transdate" : "2012-01-31T05:00:00.000Z",
"category" : "category2",
"amount" : 103
},
{
"_id" : "583f6e6d14c8042dd7c2132t6",
"transid" : 5,
"acct" : "acct2",
"transdate" : "2012-01-31T05:00:00.000Z",
"category" : "category3",
"amount" : 103
}];
let filterToApply = {
acct: {
acct1: true,
acct2: false,
acct3: true
},
initialDate: "2016-06-01",
finalDate: "2016-11-30",
category: "category3"
}
let filterData = (array, filter) => {
return array.filter( (item) => {
/* here, you iterate each item and compare with your filter,
if the item pass, you must return true. Otherwise, false */
/* e.g.: category check (if present only) */
if (filter.category && filter.category !== item.category)
return false;
}
/* add other criterias check... */
return true;
});
}
let dataFiltered = filterData(data, filterToApply);
console.log(dataFiltered);
If you want to filter an array with multiple conditions and the conditions may be optional, then use the following method.
const data = [
{ name: 'John', age: 25, city: 'New York' },
{ name: 'John', age: 25, city: 'New' },
{ name: 'Jane', age: 32, city: 'Los Angeles' },
{ name: 'Bob', age: 45, city: 'New York' },
{ name: 'Alice', age: 38, city: 'Los Angeles' }
];
const filteredData = (n, c, a) => data.filter(item => {
if (n || c || a) {
return (n ? item.name === n : true) && (c ? item.city === c : true) && (a ? item.age === a : true); // keep adding conditons as much as u want
}
});
console.log(filteredData('John', null, 25));
console.log(filteredData(null, 'Los Angeles', 38));
console.log(filteredData(null, 'Los Angeles', null));
You can chain as many as conditions

Categories

Resources