Recursively Find and Add Them as an Array - javascript

I have this kind of table (or data structure)
There is parentIdeaId that refers to the id in ideas table itself.
Here's my unstructured data (please bear in mind this is the existing data in the database, not data that is already as it is so I have to query this first under some condition)
[{
"id": "1",
"title": "Title 1",
"parentIdeaId": null
},{
"id": "2",
"title": "Title 2",
"parentIdeaId": 1
},{
"id": "3",
"title": "Title 3",
"parentIdeaId": 2
},{
"id": "4",
"title": "Title 4",
"parentIdeaId": null
}]
I also made a function to find the data (I'm using Prisma in my real code)
function find({ id }) {
return prisma.idea.findUnique({
where: {
id
}
});
}
And a function to find the children
function responseIdeas({ parentIdeaId }) {
return prisma.idea.findMany({
where: {
parentIdeaId
}
});
}
I want the data to be filtered only linked data (by their parentIdeaId) so if I query the id "1" the result would be
[{
"id": "1",
"title": "Title 1",
"parentIdeaId": null
},{
"id": "2",
"title": "Title 2",
"parentIdeaId": 1
}{
"id": "3",
"title": "Title 3",
"parentIdeaId": 2
}]
The id: "4" isn't inserted because it's not related by its parent
Here's what I made:
const data = [
{
id: "1",
title: "Title 1",
parentIdeaId: null
},
{
id: "2",
title: "Title 2",
parentIdeaId: 1
},
{
id: "3",
title: "Title 3",
parentIdeaId: 2
},
{
id: "4",
title: "Title 4",
parentIdeaId: null
}
];
function find({ id }) {
return data.find(e => e.id === id);
}
function responseIdeas({ parentIdeaId }) {
return data.filter(e => e.parentIdeaId == parentIdeaId);
}
async function detail({ id }) {
const findChildren = async ({ id }) => {
const idea = await find({ id });
const responses = await responseIdeas({ parentIdeaId: id });
if (responses.length !== 0) {
const resolveResponses = await Promise.all(
responses.map(async ({ id }) => findChildren({ id }))
);
return [idea, ...resolveResponses];
}
return { ...idea };
};
return findChildren({ id });
}
async function run() {
const result = await detail({ id: "1" });
console.dir(result, { depth: null });
}
run();
But it ended up being like this
[
{
"id": "1",
"title": "Title 1",
"parentIdeaId": null
},
[
{
"id": "2",
"title": "Title 2",
"parentIdeaId": 1
},
{
"id": "3",
"title": "Title 3",
"parentIdeaId": 2
}
]
]
Where did I mess up? Thanks

findChildren potentially returns an array of arrays due to the fact that your returning Promise.all() -> which is a array of promises and some of those promises can be arrays.
So you can use concat to merge the result into the main array e.g like this https://codesandbox.io/s/quizzical-sun-c4khx?file=/src/index.js

Related

Filter data in NodeJS

I have a json array where it contains an objects having name and rating fields.I want to filter that array where rating is 5.It might be the easy one find but I got stuck.
Below is my json object.
let products = [
{
"name" : "product 1",
"rating" : 5
},
{
"name" : "product 2",
"rating" : 4
},
{
"name" : "product 3",
"rating" : 5
},
{
"name" : "product 4",
"rating" : 2
}]
Here I am using filter functionality but unable to get How can I use it properly.
This is the function I have written but its not working.
const prod = (products) => {
products.filter((proct) => {
if(proct === 5){
console.log(proct.name);
}
});
}
prod(products);
Someone let me know what I am doing wrong.
filter function must return a boolean as result
Function is a predicate, to test each element of the array. Return a value that coerces to true to keep the element, or to false otherwise.
Array.prototype.filter
let products = [{ "name": "product 1", "rating": 5 }, { "name": "product 2", "rating": 4 }, { "name": "product 3", "rating": 5 }, { "name": "product 4", "rating": 2 } ]
products = products.filter(({rating}) => rating === 5)
console.log(products)
Each item in your array is an object, and I guess you are looking for rating===5.
Your function should be:
let products = [{ "name": "product 1", "rating": 5 }, { "name": "product 2", "rating": 4 }, { "name": "product 3", "rating": 5 }, { "name": "product 4", "rating": 2 } ]
const prod = (products) => {
products.filter((proct) => {
if (proct.rating === 5) {
console.log(proct.name);
return true;
}
return false
});
}
prod(products)
Getting just name key:
Take a look at map function which create a new array from old array:
.map(({name}) => name)
Full code:
let products = [{ "name": "product 1", "rating": 5 }, { "name": "product 2", "rating": 4 }, { "name": "product 3", "rating": 5 }, { "name": "product 4", "rating": 2 } ]
products = products.filter(({rating}) => rating === 5).map(({name}) => name)
console.log(products)
Your code is not checking the property. But the object itself. Use the . operator to check the specific property:
let products = [
{
"name" : "product 1",
"rating" : 5
},
{
"name" : "product 2",
"rating" : 4
},
{
"name" : "product 3",
"rating" : 5
},
{
"name" : "product 4",
"rating" : 2
}]
const prod = (products) => {
products.filter((proct) => {
if(proct.rating === 5){
console.log(proct.name);
}
});
}
prod(products);
Your condition is comparing proct, an object, to the value 5. You probably forgot to access the rating property of proct:
products.filter((proct) => {
if(proct.rating === 5){
console.log(proct.name);
}
});
However, you are using filter wrong. Filter is a method whose entire point is to get all the elements in the array that answer a condition and return them, and not just iterate over an array and execute code (that's what forEach is for). A more correct code would be:
const filteredProducts = products.filter((proct) => proct.rating === 5);
console.log(filteredProducts); // [{ name: "product 1", rating: 5 }, { name: "product 3", rating: 5 }]
filteredProducts.forEach((proct) => console.log(proct.name));
This solution first retrieves all the products whose rating is 5 and then iterates over each of them to print their name.

Merge two arrays and compare by ID add new Property

Hopefully someone can help me out here.
I am building an angular app with SQLite database which stores existing values. I need to compare these values from a json array received over http.
They should be matched by code, I want to compare the existing values with the update values add the property "active" = 1 otherwise active = 0 .
I tried a double foreach loop below, but I guess what's happening is the index is off so the results are not accurate in my actual application.
I have lodash available if there is some way to do this using that.
Any help would be much appreciated.
How can I get the following output
/*epected output
[{
"name": "Person 1"
"code": '001',
"active": '1'
},
{
"name": "Person 2"
"code": '002',
"active": '1'
},
{
"name": "Person 3"
"code": '003',
"active": '0' // note active 0 since doesnt exist in exsting
}
]*/
and what I tried along with 500 other things.
const existing = [{
"name": "Person 1",
"code": '001',
},
{
"name": "Person 2",
"code": '002',
},
];
const update = [{
"name": "Person 1",
"code": '001',
},
{
"name": "Person 2",
"code": '002',
},
{
"name": "Person 3",
"code": '003',
},
];
existing.forEach(element => {
update.forEach(test => {
if (element.code === test.code) {
element.avtive = true;
} else {
element.avtive = false;
}
});
return element;
});
console.log(existing);
/*epected output
[{
"name": "Person 1"
"code": '001',
"active": '1'
},
{
"name": "Person 2"
"code": '002',
"active": '1'
},
{
"name": "Person 3"
"code": '003',
"active": '0' // note active 0 since doesnt exist in exsting
}
]*/
Here is what should work for you. All existing code are extracted and then, for each updated value it is checked whether code exists in existingCodes array.
const existingCodes = existing.map((e) => e.code);
const result = updated.map((e) => ({
...e,
active: existingCodes.includes(e.code) ? '1' : '0'
});
If includes doesn't work for you on IE, you can replace this line existingCodes.includes(e.code) with existingCodes.filter((code) => code === e.code).length.
I like #radovix answer above, which worked for me, I came up with something slightly more long-winded, but which gives the same end result, but also separate lists of active and inactive:
let active = update.filter(item =>{
return existing.find(exist=> exist.code == item.code);
});
let inactive = update.filter(item =>{
return !existing.find(exist=> exist.code == item.code);
});
active = active.map(item=>({...item, active: '1'}));
inactive= inactive.map(item=>({...item, active: '0'}));
const merged = [...this.active, ...this.inactive];
You can see both ways working here: https://stackblitz.com/edit/angular-merge-arrays-update-property
You can use reduce, find and remove item from update array as
let result= existing.reduce((acc, item)=>{
let found = update.find(c => c.name == item.name);
if (found != undefined) {
const index = update.indexOf(found);
if (index > -1) {
update.splice(index, 1);
}
}
item.active = true;
acc.push(item);
return acc;
},[]);
update.map(c=> c.active = false);
//console.log(update)
result = result.concat(update);
console.log(result);
const existing = [{
"name": "Person 1",
"code": '001',
},
{
"name": "Person 2",
"code": '002',
},
];
const update = [{
"name": "Person 1",
"code": '001',
},
{
"name": "Person 2",
"code": '002',
},
{
"name": "Person 3",
"code": '003',
},
];
let result= existing.reduce((acc, item)=>{
let found = update.find(c => c.name == item.name);
if (found != undefined) {
const index = update.indexOf(found);
if (index > -1) {
update.splice(index, 1);
}
}
item.active = true;
acc.push(item);
return acc;
},[]);
update.map(c=> c.active = false);
//console.log(update)
result = result.concat(update);
console.log(result);

Flatten nested array of objects JS

I have a nested array of objects and want to get only the child property elements of an array. This is just an example and the actual data will include a unique children property in separate indices in the array. I am only able to traverse the first array in the list.
Here is my implementation:
const headers = [{
id: "name1",
title: "Name 1",
children: [{
title: "Children 1",
child: [{
title: "Child 1",
onClick: "child1Click"
},
{
title: "Child 2",
onClick: "child2Click"
}
]
},
{
title: "CHildren 2",
child: [{
title: "Child 3",
id: "child3Click"
},
{
title: "Child 4",
id: "child4Click"
}
]
}
]
},
{
id: "name2",
title: "Name 2",
children: [{
title: "Children 3",
child: [{
title: "Child 5",
onClick: "child5Click"
},
{
title: "Child 6",
onClick: "child6Click"
}
]
},
{
title: "CHildren 4",
child: [{
title: "Child 7",
id: "child7Click"
},
{
title: "Child 8",
id: "child8Click"
}
]
}
]
},
{
id: "name3",
title: "Name 3"
},
{
id: "name4",
title: "Name 4"
}
]
console.log(_.flattenDeep(_.map(_.compact(_.map(headers, item => item.children))[0], item1 => item1.child)))
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js"></script>
Expected Output:
[
{
"title": "Child 1",
"onClick": "child1Click"
},
{
"title": "Child 2",
"onClick": "child2Click"
},
{
"title": "Child 3",
"id": "child3Click"
},
{
"title": "Child 4",
"id": "child4Click"
},
{
"title": "Child 5",
"onClick": "child5Click"
},
{
"title": "Child 6",
"onClick": "child6Click"
},
{
"title": "Child 7",
"id": "child7Click"
},
{
"title": "Child 8",
"id": "child8Click"
}
]
Please advice.
Edit: I was able to get the required result using console.log(.flattenDeep(.map(.flattenDeep(.compact(_.map(headers, 'children'))), 'child')))
But is there an optimized version for doing the same? Thanks
Get the children with _.flatMap(), filter out the undefined values, and then use _.flatMap() again to get the values of the child property:
const headers = [{"id":"name1","title":"Name 1","children":[{"title":"Children 1","child":[{"title":"Child 1","onClick":"child1Click"},{"title":"Child 2","onClick":"child2Click"}]},{"title":"CHildren 2","child":[{"title":"Child 3","id":"child3Click"},{"title":"Child 4","id":"child4Click"}]}]},{"id":"name2","title":"Name 2","children":[{"title":"Children 3","child":[{"title":"Child 5","onClick":"child5Click"},{"title":"Child 6","onClick":"child6Click"}]},{"title":"CHildren 4","child":[{"title":"Child 7","id":"child7Click"},{"title":"Child 8","id":"child8Click"}]}]},{"id":"name3","title":"Name 3"},{"id":"name4","title":"Name 4"}]
const result = _.flatMap(_.compact(_.flatMap(headers, 'children')), 'child')
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js"></script>
If you're using lodash/fp, you can generate a more readable function with _.flow():
const fn = _.flow(
_.flatMap('children'),
_.compact,
_.flatMap('child')
)
const headers = [{"id":"name1","title":"Name 1","children":[{"title":"Children 1","child":[{"title":"Child 1","onClick":"child1Click"},{"title":"Child 2","onClick":"child2Click"}]},{"title":"CHildren 2","child":[{"title":"Child 3","id":"child3Click"},{"title":"Child 4","id":"child4Click"}]}]},{"id":"name2","title":"Name 2","children":[{"title":"Children 3","child":[{"title":"Child 5","onClick":"child5Click"},{"title":"Child 6","onClick":"child6Click"}]},{"title":"CHildren 4","child":[{"title":"Child 7","id":"child7Click"},{"title":"Child 8","id":"child8Click"}]}]},{"id":"name3","title":"Name 3"},{"id":"name4","title":"Name 4"}]
const result = fn(headers)
console.log(result)
<script src='https://cdn.jsdelivr.net/g/lodash#4(lodash.min.js+lodash.fp.min.js)'></script>
Using lodash with _.flow() and _.partialRight():
const pr = _.partialRight;
const fn = _.flow(
pr(_.flatMap, 'children'),
_.compact,
pr(_.flatMap, 'child')
)
const headers = [{"id":"name1","title":"Name 1","children":[{"title":"Children 1","child":[{"title":"Child 1","onClick":"child1Click"},{"title":"Child 2","onClick":"child2Click"}]},{"title":"CHildren 2","child":[{"title":"Child 3","id":"child3Click"},{"title":"Child 4","id":"child4Click"}]}]},{"id":"name2","title":"Name 2","children":[{"title":"Children 3","child":[{"title":"Child 5","onClick":"child5Click"},{"title":"Child 6","onClick":"child6Click"}]},{"title":"CHildren 4","child":[{"title":"Child 7","id":"child7Click"},{"title":"Child 8","id":"child8Click"}]}]},{"id":"name3","title":"Name 3"},{"id":"name4","title":"Name 4"}]
const result = fn(headers)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js"></script>

Use map and filter to handle items with same value

In the example below, I have an array of objects which contain some basic information about films - a film name and a personal rating.
Then i'm returning the duplicate values (using map and filter) to return new arrays and I can then count the number of items in that new array.
let films = [{
"name": "film 1",
"rating": "5",
}, {
"name": "film 2",
"rating": "1",
}, {
"name": "film 3",
"rating": "2",
}, {
"name": "film 4",
"rating": "2",
}, {
"name": "film 5",
"rating": "5",
}, {
"name": "film 6",
"rating": "4",
}];
let ratingsArray = films.map((element, i) => {
return element.rating;
})
let arr0 = ratingsArray.filter((rating) => {
return rating == 0;
})
let arr1 = ratingsArray.filter((rating) => {
return rating == 1;
})
let arr2 = ratingsArray.filter((rating) => {
return rating == 2;
})
let arr3 = ratingsArray.filter((rating) => {
return rating == 3;
})
let arr4 = ratingsArray.filter((rating) => {
return rating == 4;
})
let arr5 = ratingsArray.filter((rating) => {
return rating == 5;
});
console.log(arr0);
console.log(arr1);
console.log(arr2);
console.log(arr3);
console.log(arr4);
console.log(arr5);
This does actually work but it seems a very repetitive way of writing this code.
Can anyone please suggest a better that I could be doing this?
You could take an object and group the objects by rating.
var films = [{ name: "film 1", rating: "5", }, { name: "film 2", rating: "1", }, { name: "film 3", rating: "2", }, { name: "film 4", rating: "2", }, { name: "film 5", rating: "5", }, { name: "film 6", rating: "4", }],
ratings = Object.create(null);
films.forEach(o => (ratings[o.rating] = ratings[o.rating] || []).push(o));
console.log(ratings);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can write a function that you can use as the callback in filter. It works similarly to how lodash's pluck method worked. pluck accepts a property key, and value and returns a closure that's used as the callback function when filter iterates over the elements in the array.
let films = [
{ "name": "film 1", "rating": "5" },
{ "name": "film 2", "rating": "1" },
{ "name": "film 3", "rating": "2" },
{ "name": "film 4", "rating": "2" },
{ "name": "film 5", "rating": "5" },
{ "name": "film 6", "rating": "4" }
];
function pluck(key, value) {
return function (el) {
return el[key] === value;
}
}
const rating4 = films.filter(pluck('rating', '2'));
console.log(rating4);
You can then use this as you see fit, whether you want to loop over a set of ratings and store that information in an object, for example, but this is completely up to you. And you can use this function not just on this data set, but all sets where you need to pull out this kind of data.
let characters = [{
"name": "Batman",
"age": 62
}, {
"name": "Supergirl",
"age": 27
}, {
"name": "Tarzan",
"age": 102
}];
function pluck(key, value) {
return function (el) {
return el[key] === value;
}
}
const tarzan = characters.filter(pluck('age', 102));
console.log(tarzan);
One thing tho: you might benefit from having the ratings as integers rather than strings. One thing to consider moving forward.
You need to grouping the films by rating you can do using a object and arrays to store. See the example bellow:
let films = [{
"name": "film 1",
"rating": "5",
}, {
"name": "film 2",
"rating": "1",
}, {
"name": "film 3",
"rating": "2",
}, {
"name": "film 4",
"rating": "2",
}, {
"name": "film 5",
"rating": "5",
}, {
"name": "film 6",
"rating": "4",
}];
var filmsByRating = {};
//group films by rating
films.forEach((film) => {
if(!filmsByRating[film.rating])
filmsByRating[film.rating] = [];
filmsByRating[film.rating].push(film);
});
//print ratings and films
for(var i in filmsByRating){
console.log("Rating:", i);
console.log(filmsByRating[i]);
}
I'm going to combine concepts from other answers and suggestions therein, and generate an Array, using reduce. Some advantages of putting the ratings into an Array instead of an Object include that you will then be able to perform useful Array methods such as reverse or (custom) sort e.g. maybe you want to sort by the rating that has the most films associated with it.
var films = [
{ name: "film 1", rating: "5", },
{ name: "film 2", rating: "1", },
{ name: "film 3", rating: "2", },
{ name: "film 4", rating: "2", },
{ name: "film 5", rating: "5", },
{ name: "film 6", rating: "4", }
];
var ratings = films.reduce(function(result, film) {
var rating = parseInt(film.rating, 10);
var ratedFilms = result[rating] || [];
ratedFilms.push(film);
result[rating] = ratedFilms;
return result;
}, []);
ratings.forEach(function(rating, i) {
console.log(i + ": " + JSON.stringify(rating));
});
RESULT:
1: [{"name":"film 2","rating":"1"}]
2: [{"name":"film 3","rating":"2"},{"name":"film 4","rating":"2"}]
4: [{"name":"film 6","rating":"4"}]
5: [{"name":"film 1","rating":"5"},{"name":"film 5","rating":"5"}]

Filtering JSON with underscore

Given a data structure like so:
"items":
{
"Groups":[
{
"title":"group 1",
"SubGroups":[
{
"title":"sub1",
"id" : "1",
"items":[
{
"title":"Ajax request 1",
},
{
"title":"Ajax request 2",
}
]
},
{
"title":"sub2",
"id" : "2",
"items":[
{
"title":"Ajax request 3",
},
{
"title":"Ajax request 4",
}
]
}
]
}
]
How can I pull out all the items for a sub group based on the id? Ive treid using find like so:
var res1 = _.where(listing.items,{id:"2"});
but get an empty array returned
Thanks
Try targeting the subgroups array and then search for the id you want in there. That should then return the properties for that subgroup.
var obj = {
"Groups": [{
"title": "group 1",
"SubGroups": [{
"title": "sub1",
"id": "1",
"items": [{
"title": "Ajax request 1",
}, {
"title": "Ajax request 2",
}]
}, {
"title": "sub2",
"id": "2",
"items": [{
"title": "Ajax request 3",
}, {
"title": "Ajax request 4",
}]
}]
}]
}
then find values like this
_.where(obj.Groups[0].SubGroups, {
'id': '2'
});
Just tested and this seems to work

Categories