I need to delete an object or nested object in an array, from a given object ID.
The object needed to be deleted can both be a root object in the array or a nested object (a variant in this example) in one of the root objects.
Here's the array structure (both root objects and variant objects has unique IDs):
[
{ id: 1, title: 'object without variants', variants: [] },
{ id: 2, title: 'object with variants', variants: [{ id: 21, title: 'variant 1' }, { id: 22, title: 'variant 2' }]
]
So for example if the object ID passed from the click event that triggers the delete function is 1, I want to delete the whole root object with the ID of 1 and if the object passed from the click event is 21, I only want to delete the variant with the ID of 21 under the root object with the ID of 2 and not the whole root object.
How can this be done?
UPDATE
I got it working by using this code (passedObjectId is the ID of the object to be removed):
array = array.filter(object => object.id !== passedObjectId);
for (let object of array) {
object.variants = object.variants.filter(variant => variant.id !== passedObjectId);
}
I also need to remove the root object from the array if the last variant is removed from the object.
The code below works, but can I make this any prettier without having to use 3 filter() methods?
array = array.filter(object => object.id !== passedObjectId);
for (let object of array) {
// Remove the variant from the root object
object.variants = object.variants.filter(variant => variant.id !== passedObjectId);
// Remove the root object, if there's no variants left in it
if (!object.variants.length) {
array = array.filter(object => object.id !== passedObjectId);
}
}
ANOTHER UPDATE
I ended up using this code, that also removes a root object, if the last variant is removed:
array = array.filter(object => {
const hasRemovedVariant = object.variants.some(variant => variant.id === passedObjectId);
if (hasRemovedVariant) {
object.variants = object.variants.filter(variant => variant.id !== passedObjectId);
return object.variants.length;
}
return object.id !== passedObjectId;
});
Here is an example on how you can delete them separately, I let you put that together. If you have any question or trouble in the way to do it, feel free to ask.
const original = [{
id: 1,
title: 'object without variants',
variants: [],
},
{
id: 2,
title: 'object with variants',
variants: [{
id: 21,
title: 'variant 1'
}, {
id: 22,
title: 'variant 2'
}],
},
{
id: 3,
title: 'object with one variant',
variants: [{
id: 21,
title: 'variant 1'
}],
}
];
// Remove the root id
const rootIdToDelete = 1;
const modifiedRoot = original.filter(x => x.id !== rootIdToDelete);
// Remove the variant id
const variantToDelete = 21;
const modifiedRootAndVariant = modifiedRoot.filter((x) => {
x.variants = x.variants.filter(x => x.id !== variantToDelete);
// Keep only the roots that have at least 1 variant
return x.variants.length;
});
console.log(modifiedRootAndVariant);
You need to loop through your array and check if each object id is a match if it is the delete the object, else loop through your variants within the object and check for a match and delete the object.
Make sure you loop in reverse as you are modifying the array that you are looping through, hence the indexes change and you might get index out range exception
var myArray = getYourArray();
var idToCheck = 1; // get the id
for(int i=myArray.length;i--){
if(myArray[i].id == idToCheck){
myArray.splice(i);
}
else{
if(myArray[i].variants.length>0){
for(int j=myArray[i].variants.length;j--){
if(myArray[i].variants[j].id == idToCheck){
myArray[i].variants.splice(j);
}
}
}
}
}
This snippet will remove any child with an specified ID, no matter how big the hierarchy is.
var myArray = [{
id: 1,
title: 'object without variants',
variants: []
},
{
id: 2,
title: 'object with variants',
variants: [{
id: 21,
title: 'variant 1',
variants: [{
id: 23,
title: 'variant 1'
}, {
id: 24,
title: 'variant 2'
}]
}, {
id: 22,
title: 'variant 2'
}]
}
]
console.log(deleteByID(myArray, 21));
function deleteByID(array, id) {
for (var i = 0; i < array.length; i++) {
var item = array[i];
deleteItemByID(myArray, item, id, i);
}
return array;
}
function deleteItemByID(array, item, id, count) {
if (item.id == id) {
array.splice(count, 1);
return;
} else {
if (item.variants) {
if (typeof item.variants === "object") {
for (var i = 0; i < item.variants.length; i++) {
var varItem = item.variants[i];
deleteItemByID(item.variants, varItem, id, i);
}
}
}
}
}
You need to have a loop inside a loop.
You could use forEach for example.
var arr = [
{ id: 1, title: 'object without variants', variants: [] },
{ id: 2, title: 'object with variants', variants: [{ id: 21, title: 'variant 1' }, { id: 22, title: 'variant 2' }]}
];
var idToDelete = 21;
arr.forEach(function(obj,i){
if (obj.id == idToDelete){
arr.splice(i,1);
}
obj.variants.forEach(function(variants,i){
if (variants.id == idToDelete){
obj.variants.splice(i,1);
}
})
})
console.log(arr)
People have already answered but how about some functional, recursive and immutable code:
let array = [
{ id: 1, title: 'object without variants', variants: [] },
{ id: 2, title: 'object with variants', variants: [{ id: 21, title: 'variant 1' }, { id: 22, title: 'variant 2' }] }
];
const deleteFromArray = (arr, id) => {
if (!arr) return
let res = [];
arr.forEach((obj) => {
if (obj.id !== id) {
res.push({ ...obj, variants: deleteFromArray(obj.variants, id) })
}
})
return res;
}
console.log(JSON.stringify(deleteFromArray(array, 1)));
That way if you have some reference to the deleted object, it is deleted in ALL the objects in your array, including any nested variants.
You could create recursive function with some method so that you can exit the loop on first match and you can use splice to remove the element.
const data = [{ id: 1, title: 'object without variants', variants: [] },{ id: 2, title: 'object with variants', variants: [{ id: 21, title: 'variant 1' }, { id: 22, title: 'variant 2' }]}]
function remove(data, oid) {
data.some((e, i) => {
if(oid == e.id) return data.splice(i, 1);
if(e.variants) remove(e.variants, oid)
})
}
remove(data, 21)
console.log(data)
Related
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))
I need to filter some data inside an array of objects which is contained in another array of objects. Here is the sample structure of my data. I need to filter on categories.
[
{
id: 540,
name:'Makeup kit'
slug:'makeup-kit',
status:'publish',
categories: [
{
id: 42, name:'Fashion',slug:'fashion'
},
{
id: 43, name:'Beauty',slug:'beauty'
}
]
},
{
id: 541,
name:'Silicon gloves'
slug:'silicon-gloves',
status:'publish',
categories: [
{
id: 44, name:'Health',slug:'health'
}
]
},
{
id: 650,
name:'Julep Mask'
slug:'julep-mask',
status:'publish',
categories: [
{
id: 43, name:'Beauty',slug:'beauty'
}
]
}
]
Here is how I'm trying
beautyProducts=temp1.filter(product=>product.categories.filter(cat=>cat.id===43))
but my solution doesn't seem to work.
Array#filter() expects the function you give it to return a truthy or falsy value. Elements for which the function returns a truthy value are kept in the new array, and those that give a falsy value are removed.
You want to keep only elements for which one of the categories has an id of 43. Using a second filter, then, makes no sense here: it returns an array, and arrays are always truthy; therefore the first filter will always receive an array for each element and all elements are kept in the new array.
Instead of a second filter, you should use Array#some() - you want to know if any of the categories have id===43, and if none of them do, then you want a falsy value so that the product gets excluded from the results.
Simple change:
beautyProducts = temp1.filter(product => product.categories.some(cat => cat.id === 43))
Here is a working sample:
let temp1 = [{id:540,name:'Makeup kit',slug:'makeup-kit',status:'publish',categories:[{id:42,name:'Fashion',slug:'fashion'},{id:43,name:'Beauty',slug:'beauty'}]},{id:541,name:'Silicon gloves',slug:'silicon-gloves',status:'publish',categories:[{id:44,name:'Health',slug:'health'}]},{id:650,name:'Julep Mask',slug:'julep-mask',status:'publish',categories:[{id:43,name:'Beauty',slug:'beauty'}]}];
let beautyProducts = temp1.filter(product => product.categories.some(cat => cat.id === 43));
console.log(beautyProducts);
Try like this.
beautyProducts = temp1.map(({categories, ...others}) => {
const filteredCategories = categories.filter(cat => cat.id === 43);
return {
filteredCategories,
...others
};
}).filter(product => product.categories.length > 0)
So first, you should do the inner filter first and map the inner filtered data to the current one and do the main filter after that like above.
let data = [
{
id: 540,
name: 'Makeup kit',
slug: 'makeup-kit',
status: 'publish',
categories: [
{
id: 42, name: 'Fashion', slug: 'fashion'
},
{
id: 43, name: 'Beauty', slug: 'beauty'
}
]
},
{
id: 541,
name: 'Silicon gloves',
slug: 'silicon-gloves',
status: 'publish',
categories: [
{
id: 44, name: 'Health', slug: 'health'
}
]
},
{
id: 650,
name: 'Julep Mask',
slug: 'julep-mask',
status: 'publish',
categories: [
{
id: 43, name: 'Beauty', slug: 'beauty'
}
]
}
];
let beautyProducts = data.map(product => {
const categories = product.categories.filter(cat => cat.id === 43);
if (categories.length) {
return { ...product, categories };
}
return null;
}).filter(p => p);
console.log("Prod:", beautyProducts);
console.log(">>>>>>>>>>>>>>>>>>>>>>>>>");
let beautyProductsTwo = data.filter(product => product.categories.some(cat => cat.id === 43));
console.log("Prod ans two:", beautyProductsTwo);
I want to retrieve all child ids of a specific group, which can be deeply nested or not.
Here is a sample json:
[
{
id: 1,
name: 'Desjardins Group 1',
children: [
{ id: 2, name: 'Analysts', children: [] },
{ id: 3, name: 'Administration', children: [] }
]
},
{
id: 4,
name: 'Desjardins Group 2',
children: [
{ id: 5, name: 'Consultants1', children: [] },
{
id: 6,
name: 'Consultant2',
children: [
{
id: 7, name: 'Interns', children: [
{ id: 8, name: 'subInterns1', children: [] },
{ id: 9, name: 'subInterns2', children: [] },
{ id: 10, name: 'subInterns3', children: [] }
]
}
]
}
]
}
]
I'm trying to make a function that takes an id has a parameter, and return all child ids.
Ex: getChildGroups(6) would return 7, 8, 9 and 10.
I guess recursive function and filters are the way to go, but i can't find a proper example.
Here's a simplified version of Johann Bauer's answer.
The first function just finds the first node that matches the given ID, with no need for any accumulation of data:
function findNode(data, id) {
if (!Array.isArray(data)) return;
for (let entry of data) {
if (entry.id === id) {
return entry;
} else {
const node = findNode(entry.children, id);
if (node) {
return node;
}
}
}
}
This second function just gets the child IDs, storing them in the passed array, without any intermediate arrays being created:
function getChildIds(node, result = []) {
if (!node) return;
if (!Array.isArray(node.children)) return;
for (let entry of node.children) {
result.push(entry.id);
getChildIds(entry, result);
}
return result;
}
It might be a good idea to split your problem into two smaller problems:
Find a group of ID x somewhere nested in the graph
Given a node, return all their sub-node IDs recursively
The solution to the first problem could look something like this:
function findGroupId(o, id) {
if (o.id == id) {
// We found it!
return o;
}
if (Array.isArray(o)) {
// If we start with a list of objects, pretend it is the root node
o = {children: o}
}
let results = [];
for (let c of o.children) {
// recursively call this function again
results.push(findGroupId(c, id))
}
// return the first matching node
return results.filter(r => r !== undefined)[0];
}
And for the second problem:
function getAllChildrenIDs(o) {
if (o.children === undefined)
return [];
let ids = [];
for (c of o.children) {
ids.push(c.id);
// recursively call this function again
for (id of getAllChildrenIDs(c))
ids.push(id);
}
return ids;
}
And if we put this together:
let example = [{
id: 1,
name: 'Desjardins Group 1',
children: [{
id: 2,
name: 'Analysts',
children: []
},
{
id: 3,
name: 'Administration',
children: []
}
]
},
{
id: 4,
name: 'Desjardins Group 2',
children: [{
id: 5,
name: 'Consultants1',
children: []
},
{
id: 6,
name: 'Consultant2',
children: [{
id: 7,
name: 'Interns',
children: [{
id: 8,
name: 'subInterns1',
children: []
},
{
id: 9,
name: 'subInterns2',
children: []
},
{
id: 10,
name: 'subInterns3',
children: []
}
]
}]
}
]
}
];
function findGroupId(o, id) {
if (o.id == id) {
return o;
}
if (Array.isArray(o)) {
o = {
children: o
}
}
let results = [];
for (let c of o.children) {
results.push(findGroupId(c, id))
}
return results.filter(r => r !== undefined)[0];
}
function getAllChildrenIDs(o) {
if (o.children === undefined)
return [];
let ids = [];
for (c of o.children) {
ids.push(c.id);
for (id of getAllChildrenIDs(c))
ids.push(id);
}
return ids;
}
console.log(getAllChildrenIDs(findGroupId(example, 6)))
I have 2 arrays that i am returning...in this case Employments and Users. They both have a common field 'id' and i want to use that field to map.
I can use a for loop to map it currently but since i am looping over a nested array...i only get to catch the mapping for the first part of the array.
my json objects:
$scope.Users = [{
id: 1,
name: "Ryan"
}, {
id: 2,
name: "Julie"
}, {
id: 3,
name: "Stu"
},
{
id: 4,
name: "Holly"
}];
$scope.Employments = [{
categoriesBag: [
{
category: [
{
user_id: 1,
title: "manager"
},
{
user_id: 2,
title: "student"
}
]
},
{
category: [
{
user_id: 3,
title: "worker"
},
{
user_id: 4,
title: "facilty"
}
]
}
]
}];
the for loop that i am using to map the data:
$scope.getCategory = function(id) {
var employmentCategories = $scope.Employments.categoriesBag[0].category;
for (var i = 0; i < employmentCategories[0].category.length; i++) {
if (employmentCategories[0].category[i].user_id === id) {
return employmentCategories[0].category[i].title;
}
}
};
since i am specifying that i only want the first array employmentCategories[0], the other two users are not included in the for loop. Is there a way for me to do a loop inside of a loop to loop over only the nested categories?
You can use a nested loop
$scope.getCategory = function(id) {
for (bag in $scope.Employments.categoriesBag) {
for (category in bag.category) {
if (category.user_id == id){
return category.title
}
}
}
}
I'm trying to strip the duplicate array values from my current array. And I'd like to store the fresh list (list without duplicates) into a new variable.
var names = ["Daniel","Lucas","Gwen","Henry","Jasper","Lucas","Daniel"];
const uniqueNames = [];
const namesArr = names.filter((val, id) => {
names.indexOf(val) == id; // this just returns true
});
How can I remove the duplicated names and place the non-duplicates into a new variable?
ie: uniqueNames would return...
["Daniel","Lucas","Gwen","Henry","Jasper"]
(I'm using react jsx) Thank you!
You can do it in a one-liner
const uniqueNames = Array.from(new Set(names));
// it will return a collection of unique items
Note that #Wild Widow pointed out one of your mistake - you did not use the return statement. (it sucks when we forget, but it happens!)
I will add to that that you code could be simplified and the callback could be more reusable if you take into account the third argument of the filter(a,b,c) function - where c is the array being traversed. With that said you could refactor your code as follow:
const uniqueNames = names.filter((val, id, array) => {
return array.indexOf(val) == id;
});
Also, you won't even need a return statement if you use es6
const uniqueNames = names.filter((val,id,array) => array.indexOf(val) == id);
If you want to remove duplicate values which contains same "id", You can use this.
const arr = [
{ id: 2, name: "sumit" },
{ id: 1, name: "amit" },
{ id: 3, name: "rahul" },
{ id: 4, name: "jay" },
{ id: 2, name: "ra one" },
{ id: 3, name: "alex" },
{ id: 1, name: "devid" },
{ id: 7, name: "sam" },
];
function getUnique(arr, index) {
const unique = arr
.map(e => e[index])
// store the keys of the unique objects
.map((e, i, final) => final.indexOf(e) === i && i)
// eliminate the dead keys & store unique objects
.filter(e => arr[e]).map(e => arr[e]);
return unique;
}
console.log(getUnique(arr,'id'))
Result :
[
{ id: 2, name: "sumit" },
{ id: 1, name: "amit" },
{ id: 3, name: "rahul" },
{ id: 4, name: "jay" },
{ id: 7, name: "sam" }
]
you forgot to use return statement in the filter call
const namesArr = duplicatesArray.filter(function(elem, pos) {
return duplicatesArray.indexOf(elem) == pos;
});
Since I found the code of #Infaz 's answer used somewhere and it confused me greatly, I thought I would share the refactored function.
function getUnique(array, key) {
if (typeof key !== 'function') {
const property = key;
key = function(item) { return item[property]; };
}
return Array.from(array.reduce(function(map, item) {
const k = key(item);
if (!map.has(k)) map.set(k, item);
return map;
}, new Map()).values());
}
// Example
const items = [
{ id: 2, name: "sumit" },
{ id: 1, name: "amit" },
{ id: 3, name: "rahul" },
{ id: 4, name: "jay" },
{ id: 2, name: "ra one" },
{ id: 3, name: "alex" },
{ id: 1, name: "devid" },
{ id: 7, name: "sam" },
];
console.log(getUnique(items, 'id'));
/*Output:
[
{ id: 2, name: "sumit" },
{ id: 1, name: "amit" },
{ id: 3, name: "rahul" },
{ id: 4, name: "jay" },
{ id: 7, name: "sam" }
]
*/
Also you can do this
{Array.from(new Set(yourArray.map((j) => j.location))).map((location) => (
<option value={`${location}`}>{location}</option>
))}