How to remove data from object tree - javascript

Hi I have object like this:
Subject {
id:1
packages: [
{id:1, isChecked:true, ...},
{id:2, isChecked:false, ...},
{id:3, isChecked:true, themes:[
{id:1, isChecked:true},
{id:1, isChecked:false},
]
]
...
}
How can i remove all not checked items from this object please? I have to send this updated object to another component in react app.
The real tree looks like this:
Subject
|-packages
|-themes (check if checked)
|-themeParts (check if checked)
|-exercises (check if checked)
If any of child is checked it should be added to new component. So if I have Two packages and only one exercise is checked it is also checked themmeparts of this exercise, also theme of theme part and theme. Packages don't have isChecked attribute but i have to add this level to new object too if any of its child is checked.
Other example... if second package has no theme,part or exercise checked i have to remove from package level down alll...
So when i finish i need to have only Subject{} object with checked items + package of that checked items...
I hope i described it good XD....
anyway i tried something like this:
returnSelectedItems(){
console.log(this.state.data);
let newData = cloneDeep(this.state.data);
newData.packages = [];
this.state.data.packages.forEach((pckg) => {
const newPackage = {
};
pckg.themes.forEach((theme, key) => {
if(theme.isChecked){
}
});
});
console.log(newData);
console.log(newData.packages);
console.log(newData.packages[0].themes);
console.log(newData.packages[0].themes[0].themeParts);
}
But this is useless i think and i really don't know how to od it properly and ezy as it can be.. Thx for help

You can create a generic function like this. This takes an array as input and reduces it recursively for any nested array property. Destructure the object and get the array property to a rest object. If the rest object has any keys, recursively call the function to filter for the isChecked property.
This will work for any name of the array property for any number of nesting
function getChecked(array) {
return array.reduce((acc, { id, isChecked, ...rest }) => {
if (isChecked) {
const o = { id, isChecked };
const [key] = Object.keys(rest);
if (key)
o[key] = getChecked(rest[key]);
acc.push(o)
}
return acc;
}, [])
}
const output = {
id: input.id,
packages: getChecked(input.packages)
};
Here's a snippet:
function getChecked(array) {
return array.reduce((acc, { id, isChecked, ...rest }) => {
if (isChecked) {
const o = { id, isChecked };
const [key] = Object.keys(rest);
if (key)
o[key] = getChecked(rest[key]);
acc.push(o)
}
return acc;
}, [])
}
const input = {
id: 1,
packages: [
{ id: 1, isChecked: true },
{ id: 2, isChecked: false },
{ id: 3, isChecked: true, themes: [
{
id: 4, isChecked: true, themeParts: [
{ id: 5, isChecked: true, exercises: [
{ id: 7, isChecked: true },
{ id: 8, isChecked: false }
]
},
{ id: 6, isChecked: true }
]
},
{ id: 9, isChecked: false }
]
},
{ id: 10, isChecked: true },
]
};
const output = {
id: input.id,
packages: getChecked(input.packages)
};
console.log(output)

I believe this is what you want. That's parent cannot be removed if children(themes) need to be kept
var subjects = {
id: 1,
packages: [{
id: 1,
isChecked: true,
},
{
id: 2,
isChecked: false,
},
{
id: 3,
isChecked: true,
themes: [{
id: 1,
isChecked: true
},
{
id: 1,
isChecked: false
},
]
}
]
};
function purge(item) {
purgeItems(item.themes ||[]);
return !item.isChecked && (item.themes ||[]).length === 0;
}
function purgeItems(themes) {
var toremove = [];
themes.forEach(function(p, i) {
if (purge(p)) {
toremove.push(i)
}
});
while (toremove.length > 0) {
themes.splice(toremove.pop(), 1);
}
}
purgeItems(subjects.packages);
console.log(JSON.stringify(subjects));

This is what i needed. using this principe: let {packages: packages, ...newData} = tempData;
returnSelectedItems(){
let tempData = cloneDeep(this.state.data);
let {packages: packages, ...newData} = tempData;
this.state.data.packages.forEach(pckg => {
const {themes,...newPckg} = pckg;
newPckg.themes = [];
pckg.themes.forEach(theme =>{
if(!theme.isChecked){
return;
}
const {themeParts, ...newTheme} = theme;
newTheme.themeParts =[];
newPckg.themes.push(newTheme);
theme.themeParts.forEach(part =>{
if(!part.isChecked){
return;
}
const {knowledges, ...newPart} = part;
newPart.knowledges = [];
newTheme.themeParts.push(newPart);
part.knowledges.forEach(knowledge =>{
if(!knowledge.isChecked){
return;
}
newPart.knowledges.push(knowledge);
});
});
});
if(newPckg.themes.length > 0){
newData.packages = newPckg;
}
});
return newData;
}

Related

How to return array of objects by comparing two arrays javascript

i have two arrays Here, alreadyAddedAreas is single Array and areasData is a nested Array- Here areasData look like this.Here what i want to return data Like in same structure as Areas Data and add isAdded property if its present in alreadyAddedAreas
const areasData=[
countries: [
{
id: 123,
cities:[
{
id: 001,
areas: [{
id: 890,
}, {
id:891
}]
},
{
id: 002,
areas: [{
id: 897,
}, {
id:899
}]
},
]
}
]
]
//alreadyAddedAreas is an Array of Added Areas
const areas: [{
id: 890,
}, {
id:891
}]
// Here what i want to return data Like in same structure as Areas Data and add isAdded property if its present in alreadyAddedAreas
export const getFilteredAreasList1 = (areasData, alreadyAddedAreas) => {
const filteredArray = [];
if (areasData && alreadyAddedAreas) {
areasData[0]?.cities?.map((city) => {
return city?.areas?.map((area) => {
if (
!alreadyAddedAreas.find((item) => item?.areaID?._id === area?._id)
) {
filteredArray.push({
...area,
isAdded: false,
});
} else {
filteredArray.push({
...area,
isAdded: true,
});
}
});
});
return filteredArray;
}
};

How to check if a value in an array is present in other object and accordingly return a new object

I have an array
const dataCheck = ["Rohit","Ravi"];
I have another array of object
const userData = [
{ name: "Sagar" },
{ name: "Vishal" },
{ name: "Rohit" },
{ name: "Ravi" },
];
I want to check if any value in dataCheck is present in the userData and then return a new array with the below data
const newData = [
{ name: "Sagar" },
{ name: "Vishal" },
{ name: "Rohit", status: "present" },
{ name: "Ravi", status: "present" },
];
I tried to do something using loops but not getting the expected results
const dataCheck = ["Rohit", "Ravi"];
const userData = [
{ name: "Sagar" },
{ name: "Vishal" },
{ name: "Rohit" },
{ name: "Ravi" }
];
let newDataValue = {};
let newData = [];
userData.forEach((user) => {
const name = user.name;
dataCheck.forEach((userName) => {
if (name === userName) {
newDataValue = {
name: name,
status: "present"
};
} else {
newDataValue = {
name: name
};
}
newData.push(newDataValue);
});
});
console.log(newData);
My trial gives me repeated results multiple results which is just duplicates
You should use map() and a Set.
const dataCheck = ["Rohit","Ravi"];
const userData = [
{ name: "Sagar" },
{ name: "Vishal" },
{ name: "Rohit" },
{ name: "Ravi" },
];
const set = new Set(dataCheck);
const output = userData.map(data => set.has(data.name) ? ({...data, status: "present"}): data)
console.log(output)
.as-console-wrapper { max-height: 100% !important; top: 0; }
A Set allows for lookups in O(1) time and therefore this algorithm works in O(n) time. If you would use the array for lookups (e.g. using indcludes(), find() etc.) the runtime would be O(n²). Although this will certainly not matter at all for such small arrays, it will become more relevant the larger the array gets.
map() is used here because you want a 1:1 mapping of inputs to outputs. The only thing to determine then is, what the output should be. It is either the input, if the value is not in the Set, or it is the input extended by one property status set to "present". You can check for the presence in a Set using the has() method and can use the ternary operator ? to make the decision which case it is.
const dataCheck = ["Rohit", "Ravi"];
const userData = [
{ name: "Sagar" },
{ name: "Vishal" },
{ name: "Rohit" },
{ name: "Ravi" },
];
// map through every object and check if name property
// exists in data check with help of filter.
// if it exists the length of filter should be 1 so
// you should return { name: el.name, status: "present" } else
// return { name: el.name }
let newData = userData.map((el) => {
if (dataCheck.filter((name) => name === el.name).length > 0) {
return { name: el.name, status: "present" };
} else {
return { name: el.name };
}
});
console.log("newdata: ", newData);
A better approach would be to use map over userData array, find for matching element in dataCheck, if found return matching element + a status key or just return the found element as it is.
const dataCheck = ["Rohit","Ravi"];
const userData = [
{ name: "Sagar" },
{ name: "Vishal" },
{ name: "Rohit" },
{ name: "Ravi" },
];
const getUpdatedObject = () => {
return userData.map(userData => {
const userDetail = dataCheck.find(data => userData.name === data);
if(userDetail) return {userDetail, status:"present"}
else return {...userData}
});
}
console.log(getUpdatedObject())
Working fiddle
Loop through userData, check if name is includes in dataCheck. If true add status 'present'.
const dataCheck = ["Rohit","Ravi"];
const userData = [
{ name: "Sagar" },
{ name: "Vishal" },
{ name: "Rohit" },
{ name: "Ravi" },
];
for (let user of userData) {
if(dataCheck.includes(user.name)) {
user.status = 'present'
}
}
console.log(userData)
You are seeing repeated results due to the second loop dataCheck.forEach((userName) => { as every loop of dataCheck will fire the if/else statement and add something to the final array. However many values you add to dataCheck will be however many duplicates you get.
Only need to loop through one array and check if the value is in the other array so no duplicates get added.
const dataCheck = ["Rohit", "Ravi"];
const userData = [{ name: "Sagar" }, { name: "Vishal" }, { name: "Rohit" }, { name: "Ravi" }];
let newDataValue = {};
let newData = [];
// loop thru the users
userData.forEach((user) => {
// set the user
const name = user.name;
// check if in array
if (dataCheck.indexOf(name) >= 0) {
newDataValue = {
name: name,
status: "present",
};
}
// not in array
else {
newDataValue = {
name: name,
};
}
newData.push(newDataValue);
});
console.log(newData);
So you will do like this :
const dataCheck = ["Rohit","Ravi"];
const userData = [
{ name: "Sagar" },
{ name: "Vishal" },
{ name: "Rohit" },
{ name: "Ravi" },
];
const newUserData = userData.map( user => {
dataCheck.forEach( data => {
if( data === user.name )
user.status = "present";
});
return user;
} );
console.log( newUserData );

Update one property value of one object in array of objects, return updated array

I am trying to update one value is_deleted in an array of objects for the deleted object.
const handleDelete = (idx) => {
const filteredCards = cards.map((card, i) => {
if(i == idx) {
card.is_deleted = true;
return {...cards}
}
})
setCards(filteredCards);
}
Output
cards = [
undefined,
[
{ id: 1, is_deleted: false },
{ id: 2, is_deleted: true },
]
]
Desired Output
cards = [
{ id: 1, is_deleted: false },
{ id: 2, is_deleted: true },
]
The output is partially right - at index 1 I see the two cards, with the second is_deleted being set to true. However, at index 0 there is an "undefined".
Any insight into why this is would be helpful! Thank you and let me know if I missed a key piece of info.
You could just access the card directly, rather than using map:
let cards = [
{ id: 1, is_deleted: false },
{ id: 2, is_deleted: false },
]
const setCards = c => cards = c(cards.slice())
const handleDelete = idx => setCards(cards => (cards[idx].is_deleted = true, cards))
handleDelete(1)
console.log(cards)
The setCards function is just mimicking react's useState.

Combining two functions for working with an array of objects

I have two arrays of objects.
const details = [
{
ciphertext: 1234,
buyer: {
op_timezone: 7689,
op_city: 'Name1',
},
assignment_info: {
info: {
end_data: 1456,
start_date: 2389,
}
}
},
{
ciphertext: 5678,
buyer: {
op_timezone: 4568,
op_city: 'Name2',
},
assignment_info: {
info: {
end_data: 3467,
start_date: 8753,
}
}
},
];
const jobIds = [
{
id: 1,
},
{
id: 2,
},
];
I need to combine two arrays and take the assignment_info.info and buyer fields from each object.
function getDetailsBuyersWithJobIds(jobIds, details) {
return jobIds.map((item, index) => ({
...item,
...details[index].buyer,
}));
};
function getDetailsAssignmentInfosWithJobIds(jobIds, details) {
return jobIds.map((item, index) => ({
...item,
...details[index].assignment_info.info,
}));
};
The question is, how can two functions be combined into one?
That there would be no duplicate function, since they perform the same thing.
You can do a generic mapping function and pass it a getter function that will be able to fetch the proper data, not sure it will help the global readibility though.
What do you think about that?
const genericMapper = (getter) => (item, index) => ({
...item,
...getter(details[index]),
});
function getDetailsBuyersWithJobIds(jobIds, details) {
return jobIds.map(genericMapper(it => it.buyer));
};
function getDetailsAssignmentInfosWithJobIds(jobIds, details) {
return jobIds.map(genericMapper(it => it.assignment_info.info));
};
const details = [
{
ciphertext: 1234,
buyer: {
op_timezone: 7689,
op_city: 'Name1',
},
assignment_info: {
info: {
end_data: 1456,
start_date: 2389,
}
}
},
{
ciphertext: 5678,
buyer: {
op_timezone: 4568,
op_city: 'Name2',
},
assignment_info: {
info: {
end_data: 3467,
start_date: 8753,
}
}
},
];
const jobIds = [
{
id: 1,
},
{
id: 2,
},
];
console.log(getDetailsBuyersWithJobIds(jobIds, details));
console.log(getDetailsAssignmentInfosWithJobIds(jobIds, details));
You can add values on return object based on condition something like this
const details = [{ciphertext: 1234,buyer: {op_timezone: 7689,op_city: 'Name1',},assignment_info: {info: {end_data: 1456,start_date: 2389,}}},{ciphertext: 5678,buyer: {op_timezone: 4568,op_city: 'Name2',},assignment_info: {info: {end_data: 3467,start_date: 8753,}}},];
const jobIds = [{id: 1,},{id: 2,},];
function getDetails(jobIds, details, props = {
getBuyer: true
}) {
return jobIds.map((item, index) => ({
...item,
...(props.getBuyer && { ...details[index].buyer
}),
...(props.getAssignment && { ...details[index].assignment_info.info
})
}));
};
console.log(getDetails([1], details, {
getBuyer: true
}))
console.log(getDetails([1], details, {
getAssignment: true
}))
Here props = { getBuyer: true} used to set a default value.

Merge an item attribute of one array into items of another array

I have a few questions in regards to what would be the best approach to do the following:
Call two different API:
axios.get(contents);
axios.get(favorites);
Response will Look like this:
contents: [
{
id: 1,
value: someValue
},
{
id: 2,
value: someValue
}
];
favorites: [
{
id: 1,
contentId: 2
}
];
What would be the best approach to loop through each favorite and add an element to the contens array such as isFavorite: true when the contentId matches the id. It should look as follows:
contents: [
{
id: 1,
value: someValue
{,
{
id: 2,
value: someValue
isFavorite: true
{
];
What would be the best place to do this and is there any ES6 syntax that can easily do this? I currently have the two actions separate, one that gets the contents and one that gets the favorites, I could possibly merge those or combine them at the reducer.
Any suggestions?
You can use a Set to collect all contentId values from favorites and then iterate through your contents array. This has better time complexity than using some on an array because calling .has() on a Set is O(1):
let contents = [{
id: 1,
value: 'someValue1'
},
{
id: 2,
value: 'someValue2'
},
{
id: 3,
value: 'someValue'
}
];
let favorites = [{
id: 1,
contentId: 2
},
{
id: 2,
contentId: 3
}
];
let favoriteContents = new Set(favorites.map(f => f.contentId));
contents.forEach(c => {
if (favoriteContents.has(c.id)) c.isFavorite = true;
});
console.log(contents);
const newContents = contents.map((content) => {
const foundFavorite = favorites.find((favorite) => favorite.contentId === content.id)
if (foundFavorite) {
return {
...content,
isFavorite: true,
}
}
return content
});
You firstly need to have the promises from your API calls, and when both of them are complete you can then carry out the merge of the results.
const contentsApi = () => Promise.resolve([
{
id: 1,
value: 'foo'
},
{
id: 2,
value: 'bar'
}
])
const favouritesApi = () => Promise.resolve([
{
id: 1,
contentId: 2
}
])
let contents;
let favourites;
const contentsApiCall = contentsApi().then(res => {
contents = res;
})
const favouritesApiCall = favouritesApi().then(res => {
favourites = res;
})
Promise.all([contentsApiCall, favouritesApiCall]).then(() => {
const merged = contents.map(content => {
if(favourites.some(favourite => favourite.contentId === content.id)){
return {
...content,
isFavourite: true
}
} else {
return content;
}
})
console.log(merged)
// do whatever you need to do with your result, either return it if you want to chain promises, or set it in a variable, etc.
})

Categories