ES6 - Finding data in nested arrays - javascript

In ES6 using find or filter I'm quite comfortable iterating through to find an element in an array using a value.
However, I'm trying to get a value from a parent array based upon a value from a nested array.
For example, in this data structure:
products: [
{
id: 01,
items: [
{
id: 01,
name: 'apple'
},
{
id: 02,
name: 'banana'
},
{
id: 03,
name: 'orange'
}
]
},
{
id: 02,
items: [
{
id: 01,
name: 'carrot'
},
{
id: 02,
name: 'lettuce'
},
{
id: 03,
name: 'peas'
}
]
},
{
id: 03,
items: [
{
id: 01,
name: 'eggs'
},
{
id: 02,
name: 'bread'
},
{
id: 03,
name: 'milk'
}
]
}
]
If I know the name or id of the object milk, is there a way to find out the id of the element it's nested within?
Currently I have this:
products.find((product) => {
product.find((prod) => {
return prod.name === 'milk';
});
});
Which only returns the object containing milk.

You have to return something from the callback of the outer find. In fact, for the inner iteration you shouldn't use find but rather some that returns a boolean for whether an element matching the condition exists within the arrray:
products.find((product) => {
return product.items.some((item) => {
//^^^^^^
return item.name === 'milk';
});
});
or in short:
products.find(product => product.items.some(item => item.name === 'milk'));
Then check whether find found something (not null!) and get its .id, the result should be 03. Alternatively, you can filter for the products containing milk as an item and then map all the results to their id:
products.filter(product =>
product.items.some(item => item.name === 'milk');
).map(product =>
product.id
) // [03]

I know you mention ES6, but in this case (and if you want to return the inner object) I believe it's better using for/of instead of map/reduce/find:
for (let p of products) {
for (let i of p.items) {
if (i.name === 'milk') return i;
}
}

Another approach:
products
.map((category) => category.items)
.flat()
.find((product) => product.name === 'milk');

UPDATE
As Ricardo Marimon commented, reduce does not break so it keeps searching over the array, So with that in mind as I don't like to use for loops imperative way of programming, its possible to break early from a reduce by mutating the used array, but that would also be bad, so instead its possible to make a copy and mutate the copy instead too.
// slice creates a copy of products
return products.slice(0).reduce((prev, product, i, arr) => {
console.log(i);
const findItem = prev || product.items.find(item => item.name === 'milk');
if (typeof findItem !== 'undefined') arr.splice(1); // ejects early
return findItem;
}, undefined);
const products = [
{id: 1, items: [
{id: 1, name: 'apple'},
{id: 2, name: 'banana'},
{id: 3, name: 'orange'}
]},
{id: 2, items: [
{id: 1, name: 'carrot'},
{id: 2, name: 'lettuce'},
{id: 3, name: 'milk'}
]},
{id: 3, items: [
{id: 1, name: 'eggs'},
{id: 2, name: 'bread'},
{id: 3, name: 'peas'}
]}
];
const findItem = products.slice(0).reduce((prev, product, i, arr) => {
console.log(i);
const findItem = prev || product.items.find(item => item.name === 'milk');
if (typeof findItem !== 'undefined') arr.splice(1); // ejects early
return findItem;
}, undefined);
console.log(findItem);
OLD
The accepted answer didn't do it for me because I wanted the result of the inner find, using both it always gave me the result of the outer filter/find, and I had to use the resulting array to find the value again.
So instead I used reduce with short-circuit to get the inner result.
// undefined as the initial value is necessary, otherwise it gets the first value of the array instead.
return products.reduce((prev, product) => prev || product.items.find(item => item.name === 'milk'), undefined);
const products = [
{id: 1, items: [
{id: 1, name: 'apple'},
{id: 2, name: 'banana'},
{id: 3, name: 'orange'}
]},
{id: 2, items: [
{id: 1, name: 'carrot'},
{id: 2, name: 'lettuce'},
{id: 3, name: 'peas'}
]},
{id: 3, items: [
{id: 1, name: 'eggs'},
{id: 2, name: 'bread'},
{id: 3, name: 'milk'}
]}
];
console.log(products.reduce((prev, product) => prev || product.items.find(item => item.name === 'milk'), undefined));

To get the item directly without doubling back to get the id/object:
const products = [
{ id: 01,
items: [ { id: 01, name: 'apple' },
{ id: 02, name: 'banana'},
{ id: 03, name: 'orange'}]},
{ id: 02,
items: [ { id: 01, name: 'carrot' },
{ id: 02, name: 'lettuce'},
{ id: 03, name: 'peas' }]},
{ id: 03,
items: [ { id: 01, name: 'eggs' },
{ id: 02, name: 'bread' },
{ id: 03, name: 'milk' }]}
]
let found;
for ( const category of products ){
found = category.items.find( item => item.name == "milk" )
if ( found ) break
}
console.log( found )
// == { id: 3, name: 'milk' }

Related

How to change value in array with objects in JS

I have an array and want to change name in object { id: 4, name: 'name4' } to 'name6'
const example = [
{
id: '1234',
desc: 'sample1',
items: [
{ id: 1, name: 'name1' },
{ id: 2, name: 'testItem2' }
]
},
{
id: '3456',
desc: 'sample2',
items: [
{ id: 4, name: 'name4' },
{ id: 5, name: 'testItem5' }
]
},
I try in this way but it isn't working
const name = 'name4';
const result = example?.forEach((group) =>
group.items.forEach((item) =>
if (item.name === name) {
return item.name === 'name6';
}
return null;
})
);
The for...of statement is my recommendation for readability and loop optimisation.
const example = [
{
id: '1234',
desc: 'sample1',
items: [
{ id: 1, name: 'name1' },
{ id: 2, name: 'testItem2' },
],
},
{
id: '3456',
desc: 'sample2',
items: [
{ id: 4, name: 'name4' },
{ id: 5, name: 'testItem5' },
],
},
];
const oldName = 'name4';
const newName = 'name6';
for (const group of example) {
for (const item of group.items) {
if (item.name === oldName) {
item.name === newName;
break
}
}
}
You could even go a step further and terminate the outer loop with a label if you only need to change the name in a single group.
outerLoop: for (const group of example) {
for (const item of group.items) {
if (item.name === oldName) {
item.name === newName;
break outerLoop;
}
}
}
Hope this helps.
You could either change the value by simply assigning a new value.
example[1].items[0].name = 'name6'
But you can also iterate through all items and search for the name you want to change. I created a function that goes through an array and loops over its nested items arrays searching for any given name (targetName) and replacing it with a new one (newName):
function changeName(array, targetName, newName) {
// Loop through the elements of array
array.forEach((element) => {
// Check each item: change the name if it matches the target
element.items.forEach((item) => {
if (item.name === targetName) item.name = newName;
});
});
}
// This function will check example array and change
// every name that has a value 'name4' into 'name6'
changeName(example, "name4", "name6");
forEach doesn't return any value.
Instead of return item.name === 'name6' you can simply set new value to item.name.
Why not like this?
const example = [{
id: '1234',
desc: 'sample1',
items: [{
id: 1,
name: 'name1'
},
{
id: 2,
name: 'testItem2'
}
]
},
{
id: '3456',
desc: 'sample2',
items: [{
id: 4,
name: 'name4'
},
{
id: 5,
name: 'testItem5'
}
]
},
]
example[1].items[0].name = 'name6'
console.log(example)

How to splice multiple values from array of objects which matches given nested array

I am trying to remove the array of objects if the given array of objects matches with the index but it is only removing the last index value.
How we can remove multiple values?
let idArr = [[{ index: 2 }], [{ index: 3 }]];
let obj = [
{
id: 1,
name: 'abc',
},
{
id: 2,
name: 'abc',
},
{
id: 3,
name: 'abc',
},
{
id: 4,
name: 'abc',
},
];
let data = obj.filter((item, i) =>
idArr.reduce((val) => val.find(({ index }) => i === index))
);
//expected output
[
{
id: 1,
name: 'abc',
},
{
id: 2,
name: 'abc',
},
];
I think that following code achieves what you're expecting
let data = obj.filter((obj, idx) => !idArr.find(id => id[0].index === idx));

Remove a specific object from an object array, filtering by a key and value pair

Is there any quick way to remove a specific object from an object array, filtering by a key and value pair, without specifying an index number?
For example, if there was an object array like so:
const arr = [
{ id: 1, name: 'apple' },
{ id: 2, name: 'banana' },
{ id: 3, name: 'cherry' },
...,
{ id: 30, name: 'grape' },
...,
{ id: 50, name: 'pineapple' }
]
How can you remove only the fruit which has the id: 30 without using its index number?
I have already figured out one way like the following code, but it looks like a roundabout way:
for ( let i = 0; i < arr.length; i++) {
if ( arr[i].id === 30 ) {
arr.splice(i, 1);
}
}
with es6 standard, you can use the filter method
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
const arr = [
{ id: 1, name: 'apple' },
{ id: 2, name: 'banana' },
{ id: 3, name: 'cherry' },
{ id: 30, name: 'grape' },
{ id: 50, name: 'pineapple' }
];
// !== for strict checking
console.log(arr.filter(e => e.id !== 30))

Turn array of objects into structured object? [duplicate]

I am working on a Nodejs project. I have to create a function which takes an object (a child category) like:
{
id: 65,
name: 'Outdoor',
parent_id: 2
}
Now I want my function to check for the parent category by using parent_id from database and return an array/object like this:
{
id: 2,
name: 'Furniture',
parent: {
id: 1,
name: 'Residential',
parent: {
id: ...,
name: ...,
parent: {
and so on..
}
}
}
}
This is what I have done so far:
* _get_category_parents(category, _array) {
if(_array === undefined) _array = []
if( category.parent_id !== 0 ) {
const c_parent = yield this.Database.from('categories').where('id', '=', category.parent_id)
_array.push({id: c_parent[0].id, name: c_parent[0].name})
yield this._get_category_parents(c_parent[0], _array)
}
return _array
}
And calling this function like this:
const parents = yield this._get_category_parents(category)
This returns me an array of parents like this:
[
{
"id": 2,
"name": "Furniture"
},
{
"id": 1,
"name": "Residential"
}
]
I want Residential object to be appended in Furniture's parent node.
I have spent too much time on this but not getting what I want. Any help would be deeply appreciated.
What you want to think about is a recursive solution.
Since you're calling a database, it's probably unlikely, but if the lookup by id is synchronous, you might do it with code something like the following (note that I'm faking a db here):
const getHierarchy = (lookup, child) => {
const {id, name, parent_id} = lookup(child) ||
{id: null, name: null, parent_id: 0}
return parent_id == 0
? {id, name, parent_id}
: {...{id, name}, ...{parent: getHierarchy(lookup, {parent_id})}}
}
const items = [
{id: 1, name: 'Residential', parent_id: 5},
{id: 2, name: 'Furniture', parent_id: 1},
{id: 3, name: 'Other', parent_id: 0},
{id: 4, name: 'FooBar', parent_id: 3},
{id: 5, name: 'Stuff', parent_id: 0}
]
const lookup = child => items.find(item => item.id == child.parent_id)
const item = {id: 65, name: 'Outdoor', parent_id: 2}
console.log(getHierarchy(lookup, item))
You would have to write an appropriate lookup function, presumably using this.Database.from(...). You might also want to simplified version that bakes in your lookup function, in which case, you might write
const getAncestry = (item) => getHierarchy(lookup, item)
If, as seems more likely, your lookup is asynchronous, then that will affect getHierarchy and how you call it. Here's one possibility:
const getHierarchy = async (lookup, child) => {
const {id, name, parent_id} = await lookup(child) ||
{id: null, name: null, parent_id: 0}
return parent_id == 0
? {id, name, parent_id}
: {...{id, name}, ...{parent: await getHierarchy(lookup, {parent_id})}}
}
const items = [
{id: 1, name: 'Residential', parent_id: 5},
{id: 2, name: 'Furniture', parent_id: 1},
{id: 3, name: 'Other', parent_id: 0},
{id: 4, name: 'FooBar', parent_id: 3},
{id: 5, name: 'Stuff', parent_id: 0}
]
const lookup = async child => new Promise(
(resolve, reject) => setTimeout(
() => resolve(items.find(item => item.id == child.parent_id)),
1000
)
)
const getAncestry = async item => getHierarchy(lookup, item)
const item = {id: 65, name: 'Outdoor', parent_id: 2}
getAncestry(item).then(console.log)
Note the change in how you call the function. You need to call .then() on the resulting promise to get any useful behavior.

nested array object comparison with another array of elements and create new array using Javascript or ES6

I have a complex array's like shown below
sectionDetail = [{id: 1, name:'ma'}, {id: 2, name:'na'}, {id: 3, name:'ra'}, {id: 4, name:'ka'}, {id: 5, name:'pa'}];
abc = [{id:'1', name:'zam', sections:['1',4]}, {id:'2', name:'dam', sections:['3']}, {id:'3', name:'nam', sections:['2','4']}];
Now I have to loop through the abc with respect to sections to replace the array elements with their respective sectionDetail values
I have tried by looping it to a new variable but my sections is getting replaced every time. below is the code i tried.
const matchingBoost = [];
const getCategoryBasedBoostList = [];
abc.forEach((item, i) => {
sectionDetail.forEach((val, index) => {
item.section.forEach((value, x) => {
if (value == val.Id) {
matchingBoost.push(val);
}
});
});
getCategoryBasedBoostList.push({
Name: item.Name,
Boost: matchingBoost
});
});
so basically I'm looking for a new array something like this
xyz = [{name:'zam', sections:[{id: 1, name:'ma'}, {id: 4, name:'ka'}]},
{name:'dam', sections:[{id: 3, name:'ra'}]}, {name:'nam', sections:[{id: 2, name:'na'}, {id: 4, name:'ka'}]}];
hoping I made sense and hoping for some response.
You can basically filter the sections from sectionDetail based on whether the object.id inside it is included in the sections of abc. I have mapped the indexes to number in both cases since one was string and the other was integer.
sectionDetail = [{id: 1, name:'ma'}, {id: 2, name:'na'}, {id: 3, name:'ra'}, {id: 4, name:'ka'}, {id: 5, name:'pa'}];
abc = [{id:'1', name:'zam', sections:['1',4]}, {id:'2', name:'dam', sections:['3']}, {id:'3', name:'nam', sections:['2','4']}];
xyz = abc.map(item => ({...item, sections: sectionDetail.filter(sect => item.sections.map(id => parseInt(id)).includes(parseInt(sect.id)))}));
console.log(xyz);
You could take a Map and then map the data with the items of sectionDetail.
var sectionDetail = [{ id: 1, name: 'ma' }, { id: 2, name: 'na' }, { id: 3, name: 'ra' }, { id: 4, name: 'ka' }, { id: 5, name: 'pa' }],
data = [{ id: '1', name: 'zam', sections: ['1', 4] }, { id: '2', name: 'dam', sections: ['3'] }, { id: '3', name: 'nam', sections: ['2', '4'] }],
map = new Map(sectionDetail.map(o => [o.id, o])),
result = data.map(({ name, sections }) =>
({ name, sections: sections.map(id => map.get(+id)) })
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
So you want to remove the id from the abc objects and replace the sections array elements with the corresponding details objects? This looks like a job for forEach and map! The code I'm about to show also does a little bit of pre-processing of the sections array to make the overall code more efficient.
const sections = sectionDetail.reduce((result, section) => {
result[section.id] = section;
return result;
}, {});
abc.forEach(item => {
delete item.id;
item.sections = item.sections.map(id => sections[id]);
});
Try like this:
const sectionDetail = [
{ id: 1, name: 'ma' },
{ id: 2, name: 'na' },
{ id: 3, name: 'ra' },
{ id: 4, name: 'ka' },
{ id: 5, name: 'pa' }];
const abc = [
{ id: '1', name: 'zam', sections: ['1', 4] },
{ id: '2', name: 'dam', sections: ['3'] },
{ id: '3', name: 'nam', sections: ['2', '4'] }
];
const desired = abc.map(({id, name, sections}) => {
return {id, name, sections : sectionDetail.filter(f => {
return sections.map(s => +s).includes(f.id)
})};
})
console.log(desired);
where +s is casting to Number type.

Categories