Filtering and maping in nested arrays - javascript

My task is to filter objects by values in nested arrays. like in example below:
const array = [
{
authorId: '62222a1cea00a0601f200142',
description: [
[
{
title: 'English description',
paragraph: 'And english paragraph!!!!',
},
],
[
{
title: 'some title!!!',
paragraph: 'some para!!',
},
],
],
removed: false,
status: 'NEW',
},
{
authorId: '621f97562511255efa0f135e',
description: [
[
{
title: 'EEEE',
paragraph: 'aaaa',
},
],
],
removed: false,
status: 'NEW',
},
{
description: [
[
{
title: 'TEST',
paragraph: 'TESR',
},
],
],
removed: false,
status: 'NEW',
},
{
authorId: '621f97432511255efa0f135c',
description: [
[
{
title: 'My task',
paragraph: 'Parapgraph 19',
},
],
],
removed: false,
status: 'NEW',
},
]
my expected results is something like that, based on search input, which is conts searchInput
const searchInput = "par"
const array = [
{
authorId: '62222a1200142',
description: [
[
{
title: 'English description',
paragraph: 'And english paragraph!!!!',
},
],
[
{
title: 'some title!!!',
paragraph: 'some para!!',
},
],
],
removed: false,
status: 'NEW',
},
{
authorId: '6a000142',
description: [
[
{
title: 'TEST',
paragraph: 'paragraph one',
},
],
],
removed: false,
status: 'NEW',
},
]
Ive already try something like this:
const results = array?.map((el) => el?.description.map((i) => i.map((item) => item.paragraph))).filter((description) =>description.toString().toLowerCase().includes(searchValue))
But it return only arrays with paragraphs and i expected to filter whole objects, with all data, not only strings

You need to put the map inside the filter, so your data isn't modified. You use first map the description paragraphs into an array and check if all the values of the paragraphs includes the searched param.
const searchValue = 'para';
const array = [{authorId: '62222a1cea00a0601f200142',description: [[{title: 'English description',paragraph: 'And english paragraph!!!!',},],[{title: 'some title!!!',paragraph: 'some para!!',},],],removed: false,status: 'NEW',},{authorId: '621f97562511255efa0f135e',description: [[{title: 'EEEE',paragraph: 'aaaa',},],],removed: false,status: 'NEW',},{description: [[{title: 'TEST',paragraph: 'TESR',},],],removed: false,status: 'NEW',},{authorId: '621f97432511255efa0f135c',description: [[{title: 'My task',paragraph: 'Parapgraph 19',},],],removed: false,status: 'NEW',},];
const results = array.filter(el => {
return el.description
.map(i => {
return i.map(item => item.paragraph)
})
.every((description) => {
return description.toString().toLowerCase().includes(searchValue)
})
});
console.log(results);
I didn't tested the function, however it should work. Put the questionmarks when needed.

I think the filter should be on the array itself, since that is what you expect to be the result.
I used the some function to resolve the arrays in the object.
array?.filter(el=>el?.description.some(el2=>el2.some(el3=>el3?.paragraph.toString().toLowerCase().includes(searchValue))))

It's easier to understand if you don't have everything on one line.
Use filter to return a new array of objects where the description (inner) array has an at leasr one object that contains a paragraph containing the query.
const array=[{authorId:"62222a1cea00a0601f200142",description:[[{title:"English description",paragraph:"And english paragraph!!!!"}],[{title:"some title!!!",paragraph:"some para!!"}]],removed:!1,status:"NEW"},{authorId:"621f97562511255efa0f135e",description:[[{title:"EEEE",paragraph:"aaaa"}]],removed:!1,status:"NEW"},{description:[[{title:"TEST",paragraph:"TESR"}]],removed:!1,status:"NEW"},{authorId:"621f97432511255efa0f135c",description:[[{title:"My task",paragraph:"Parapgraph 19"}]],removed:!1,status:"NEW"}];
const query = 'par';
const out = array.filter(outer => {
// Return an object when the inner array
// of the function has some object that
// contains a paragraph containing the query
return outer.description.some(arr => {
return arr.some(inner => {
return inner.paragraph
.toLowerCase()
.includes(query);
});
});
});
console.log(out);
Additional documentation
some

have you tried .reduce()? It is combination of .map() and .filter() as you can see here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
Takes some time to get into .reduce(), but once you get it, you will like it!

Related

Map array of objects into new groups

I have an object with two arrays of objects being returned from an API. I need to try to map this into a new array of objects so I can group the data for the Vue v-select component. CodePen Example
fields: {
current: [
{
name: 'Date'
},
{
name: 'Time'
}
],
previous: [
{
name: 'Birthday'
},
{
name: 'Comments'
}
]
},
How can I map this into a new array of objects that looks like this?
grouped: [
{
group: "Current",
},
{
name: "Date"
},
{
name: "Time"
},
{
group: "Previous"
},
{
name: "Birthday"
},
{
name: "Comments"
},
]
Use Object.entries() to get the groups, and their values, and map them using Array.flatMap(). Create the group's object, and add it to an array with the group's items.
const flattenGroups = fields =>
Object.entries(fields)
.flatMap(([group, items]) => [{ group }, ...items])
const fields = {"current":[{"name":"Date"},{"name":"Time"}],"previous":[{"name":"Birthday"},{"name":"Comments"}]}
const result = flattenGroups(fields)
console.log(result)

Creating filtered and simplified data from deeply nested array of objects

I have somewhat complex set of data that I would need to simplify and split into two different sets, based on a comparison on nested object property value.
Here's the data structure I'm working with:
[
{
object: { id: 123 },
group: [
{
events: [
{
type: 'type',
schedule: [
{
time: '2019-10-30T09:30:00+00:00',
},
{
time: '2019-10-21T21:00:00+00:00',
}
],
},
{
type: 'type',
schedule: [
{
time: '2019-10-24T09:30:00+00:00',
},
{
time: '2019-09-22T21:00:00+00:00',
},
],
},
],
},
],
},
]
Most important thing would be to split this data into two different sets (arrays of objects), based on whatever or not the schedule value of time is in the past or in the future. It would also help to simplify the structure as a whole. There is a lot of properties attached to the objects and I'm fine brining in the whole object instead of just cherry-picking the important ones, as long as the nesting is logical and usable.
So something like this for the "past" data would be ideal:
[
{
id: 123,
events: [
{
type: 'type',
schedule: [
{
time: '2019-10-21T21:00:00+00:00',
}
],
},
{
type: 'type',
schedule: [
{
time: '2019-09-22T21:00:00+00:00',
}
],
}
]
}
]
I've been trying to use the different array methods (filter and map) to spit out something suitable for my needs, but can't figure out how to do it right. Mostly how to filter based on a nested value and copy over all the nesting structure on a match.
You'll need map() to restructure your array, spread syntax to iterate over object properties in-place, and filter() to filter the data based on a condition.
Using the code below, you can get an array of all past events. You can modify the condition accordingly to get all future events.
// mapping from old data array
const pastData = data.map(({object, group}) => ({
// get all properties of the nested "object"
...object,
// map "events" into a new structure
events: group[0].events.map((event) => (
{
// get all other properties
...event,
// include only those "schedule" objects where time is less than current date
schedule: event.schedule.filter(schedule => new Date(schedule.time) < new Date()),
}
)),
}));
Here's a working example:
const data = [
{
object: { id: 123 },
group: [
{
events: [
{
type: 'type',
schedule: [
{
time: '2019-10-30T09:30:00+00:00',
},
{
time: '2020-10-21T21:00:00+00:00',
}
],
},
{
type: 'type',
schedule: [
{
time: '2019-10-24T09:30:00+00:00',
},
{
time: '2020-09-22T21:00:00+00:00',
},
],
},
],
},
],
},
];
const pastData = data.map(({object, group}) => ({
...object,
events: group[0].events.map((event) => (
{
...event,
schedule: event.schedule.filter(schedule => new Date(schedule.time) < new Date()),
}
)),
}));
const futureData = data.map(({object, group}) => ({
...object,
events: group[0].events.map((event) => (
{
...event,
schedule: event.schedule.filter(schedule => new Date(schedule.time) >= new Date())
}
))
}));
console.log('past data:', pastData, ', future data:', futureData);
Try to use map method and spread operator:
const result = data.map(({object, group}) =>
({ ...object, events: group.map(g=> g.events)}));
const data =
[
{
object: { id: 123 },
group: [
{
events: [
{
type: 'type',
schedule: [
{
time: '2019-10-30T09:30:00+00:00',
},
{
time: '2019-10-21T21:00:00+00:00',
}
],
},
{
type: 'type',
schedule: [
{
time: '2019-10-24T09:30:00+00:00',
},
{
time: '2019-09-22T21:00:00+00:00',
},
],
},
],
},
],
},
];
const result = data.map(({object, group}) => ({ ...object, events: group.map(g=> g.events)}));
console.log(result)

Sequelize relationship query returns duplicate data

I'm querying customer orders for a specified customer using Sequelize relationships.
index.js
var results2 = await customerService.getOrders(1);
console.log(results2);
service.js
exports.getOrders = function (id) {
return customerModel.findAll({
raw: true,
include: [{
model: orderModel,
where: { customer_idcustomer: id }
}],
}).then(r => r);
};
results
[ { idcustomer: 1,
customername: 'hello world',
'orders.idorder': 1,
'orders.orderdesc': 'order description 1',
'orders.customer_idcustomer': 1 },
{ idcustomer: 1,
customername: 'hello world',
'orders.idorder': 2,
'orders.orderdesc': 'Test 456',
'orders.customer_idcustomer': 1 },
{ idcustomer: 1,
customername: 'hello world',
'orders.idorder': 3,
'orders.orderdesc': 'Test 123',
'orders.customer_idcustomer': 1 } ]
expected
[ { idcustomer: 1,
customername: 'hello world',
'orders: [{
'orders.idorder': 1,
'orders.orderdesc': 'order description 1',
'orders.customer_idcustomer': 1 },
},
{
'orders.idorder': 2,
'orders.orderdesc': 'order description 2',
'orders.customer_idcustomer': 1 },
},
{
'orders.idorder': 3,
'orders.orderdesc': 'order description 3',
'orders.customer_idcustomer': 1 },
}]
]
All you need is to remove raw: true, from query ,
as it will return plain/flat object , and that will convert your object as it looks now.
exports.getOrders = function (id) {
return customerModel.findAll({
// raw: true, // <------ Just remove this line
include: [{
model: orderModel,
where: { customer_idcustomer: id }
}],
}).then(r => r);
};
Note : You should put the where condition in upper level as per your
logic
exports.getOrders = function (id) {
return customerModel.findAll({
where: { id: id } ,
// raw: true, // <------ Just remove this line
include: [{
model: orderModel
}]
}).then(r => r);
};
Try removing raw key value from your query.
Finder methods are intended to query data from the database. They do
not return plain objects but instead return model instances. Because
finder methods return model instances you can call any model instance
member on the result as described in the documentation for instances.
If you want to get the data without meta/model information then map your results using
{ plain: true }
Good sequelize examples in docs
Example:
const getPlainData = records => records.map(record =>
record.get({ plain: true }));
// Your code
return customerModel.findAll({
// raw: true, <= remove
include: [{
model: orderModel,
where: { customer_idcustomer: id }
}],
}).then(getPlainData);
In my case, having
raw: true
in the options didn't make any difference.
I added
distinct: true
and the issue disappeared.
I was using findAndCountAll, though.
Documentation: https://sequelize.org/master/class/lib/model.js~Model.html

Ramda.js Combine two array of objects that share the same property ID

I have these two array of objects
todos: [
{
id: 1,
name: 'customerReport',
label: 'Report send to customer'
},
{
id: 2,
name: 'handover',
label: 'Handover (in CRM)'
},
]
And:
todosMoreDetails: [
{
id: 1,
checked: false,
link: {
type: 'url',
content: 'http://something.com'
},
notes: []
},
{
id: 2,
checked: false,
link: {
type: 'url',
content: 'http://something.com'
},
notes: []
}
]
So that the final array of objects will be a combination of the two, based on the object ID, like below:
FinalTodos: [
{
id: 1,
checked: false,
link: {
type: 'url',
content: 'http://something.com'
},
notes: [],
name: 'customerReport',
label: 'Report send to customer'
},
{
id: 2,
checked: false,
link: {
type: 'url',
content: 'http://something.com'
},
notes: [],
name: 'handover',
label: 'Handover (in CRM)'
}
]
I tried with merge mergeAll and mergeWithKey but I am probably missing something
You can achieve this with an intermediate groupBy:
Transform the todosMoreDetails array into an object keyed by todo property ID using groupBy:
var moreDetailsById = R.groupBy(R.prop('id'), todosMoreDetails);
moreDetailsById is an object where the key is id, and the value is an array of todos. If the id is unique, this will be a singleton array:
{
1: [{
id: 1,
checked: false,
link: {
type: 'url',
content: 'http://something.com'
},
notes: []
}]
}
Now transform the todos array by merging each todo to it's details you retrieve from the grouped view:
var finalTodos = R.map(todo => R.merge(todo, moreDetailsById[todo.id][0]), todos);
An alternate more detailed way:
function mergeTodo(todo) {
var details = moreDetailsById[todo.id][0]; // this is not null safe
var finalTodo = R.merge(todo, details);
return finalTodo;
}
var moreDetailsById = R.groupBy(R.prop('id'), todosMoreDetails);
var finalTodos = todos.map(mergeTodo);
I guess merge is only used for arrays. Have a search for object "extend". Maybe storing the todo details not in seperate objects is the better solution.
Using jQuery? https://api.jquery.com/jquery.extend/
Using underscore? http://underscorejs.org/#extend
Native approach? https://gomakethings.com/vanilla-javascript-version-of-jquery-extend/
Using underscore:
var result = [];
var entry = {};
_.each(todos, function(todo) {
_.each(todosMoreDetails, function(detail) {
if (todo.id == detail.id) {
entry = _.extend(todo, detail);
result.push(entry);
}
}
});
return result;

ImmutableJS - update value in a List

I have a Map like this (in ImmutableJS):
{arrayOfValues: [
{one: {inside: 'first in array'}},
{one: {inside: 'second in array'}}
]}
And I want to update the value "inside" in the second entry in the "arrayOfValues" array. How can I do it? This is what I have now and it says "Uncaught Error: invalid keyPath"
theMap.update('arrayOfValues',(list)=>{
return list.setIn([1,'one','inside'],'updated value');
})
I also tried directly this and it didn't work:
theMap.setIn(['arrayOfValues',1,'one','inside'],'updated value');
After several hours of looking for the solution, I appreciate any help. Thank you.
What you are doing is correct (see this JSBin).
const orig = Immutable.fromJS({
arrayOfValues: [
{ one: { inside: 'first in array' } },
{ one: { inside: 'second in array' } },
]
});
const updated = orig.setIn(['arrayOfValues', 1, 'one', 'inside'], 'updated value');
console.log(updated.toJS());
// {
// arrayOfValues: [
// { one: { inside: 'first in array' } },
// { one: { inside: 'second in array' } },
// ]
// }
When you call orig.setIn(), it doesn't modify orig directly. That's the whole purpose of this Immutable library. It doesn't mutate the existing data but creates a new one from the existing one.
Your setIn example works as you should see in this plunkr:
http://plnkr.co/edit/1uXTWtKlykeuU6vB3xVO?p=preview
Perhaps you are assuming the value of theMap will be changed as a result of the setIn?
As these structures are immutable, you must capture the modified value in a new variable as var theMap2 = theMap.setIn(['arrayOfValues',1,'one','inside'],'updated value');
activePane is the index of Object in Array(List) that I had to modify
case CHANGE_SERVICE:
var obj = {
title: '1212121 Tab',
service: '',
tagName: '',
preDefinedApi: '',
methodType: '',
url: '',
urlParams: [{
label: '',
name: '',
value: '',
}],
headers: [{
label: '',
name: '',
value: '',
}],
};
var activePane = state.get('activePane');
var panes = state.setIn(['panes', activePane, 'service'], action.val);
return state.setIn(['panes', activePane, 'service'], action.val);

Categories