Prevent Assigned by Reference - javascript

I am attempting to clone a few objects in an array based on some properties.
Given an array of objects:
[
{
id: 1,
data: {
user:
{
user1: 0,
user2: 1,
}
}
},
{
id: 2,
data: {
user:
{
user1: 0,
}
}
},
]
I want to transform the above into:
[
{
id: 1,
data: {
user: 'user1',
user_status: 0
}
},
{
id: 1,
data: {
user: 'user2',
user_status: 1,
}
},
{
id: 2,
data: {
user: 'user1',
user_status: 0,
}
},
]
Every user object within the array should have its user property transformed regardless of how many properties are in the user object. There's other properties in the data object that I want to copy but do not wish to modify.
The closest I got was:
result.rows.forEach((item, index) => {
for ( const user in item.data.user ) {
const notif = Object.assign({}, item);
notif.data.user = user;
notif.data.user_status = item.data.user[user];
result.rows.push(notif);
}
});
However, the above acts as if notif it is assigned by reference(?) and is mutating the original object. Using a console.log during the for in loop results in:
console.log(notif.id, notif.data.user, notif.data.user_status)
// Results in 1, user1, undefined
console.log(item.data.user, item.data.user[user])
// Results in user1, undefined instead of the expect { 'user1': 0 }
This results in an array like:
{
id: 1,
data: {
user: 'user2', // Should be user1
user_status: undefined, // Should be 0
}
},
{
id: 1,
data: {
user: 'user2', // Should be user2 -- hooray but in a bad way
user_status: undefined, // Should be 1
}
}
All of this is running on a Node.js (8.11.1) server.

The data in your item references the original data object, because Object.assign gives a shallow clone, not a deep clone.
This would probably be achieved most elegantly by reduce-ing into an array in one go, extracting all the primitives immediately, rather than trying to work with the (by-reference) objects:
const input=[{id:1,data:{user:{user1:0,user2:1,}}},{id:2,data:{user:{user1:0,}}},]
const output = input.reduce((a, { id, data: { user: users }}) => {
Object.entries(users).forEach(([user, user_status]) => {
a.push({ id, data: { user, user_status }});
});
return a;
}, []);
console.log(output);
A fix to your original code would involve cloning the data property as well:
const input=[{id:1,data:{user:{user1:0,user2:1,}}},{id:2,data:{user:{user1:0,}}},]
const output = [];
input.forEach((item, index) => {
for (const user in item.data.user) {
const notif = Object.assign({}, item);
notif.data = Object.assign({}, item.data);
notif.data.user = user;
notif.data.user_status = item.data.user[user];
output.push(notif);
}
});
console.log(output);

You can achieve this using two .forEach loops. You can't loop with a .forEach inside an object, instead, we will loop over Object.keys of the object. Which is essentially an array of keys.
Then for each fragment, for example
{
id: 1,
data: {
user: 'user1',
user_status: 0
}
}
we can push a brand new object to res.
Here is the code:
let res = [];
data.forEach((e, i, arr) => Object.keys(e.data.user).forEach((k, j) => {
res.push({
id: e.id,
data: {
user: k,
user_status: j
}
});
}));
console.log(res);
<script>
const data=[{id:1,data:{user:{user1:0,user2:1,}}},{id:2,data:{user:{user1:0,}}}];
</script>

One way would be to transform each object in the array using map into a subarray of new objects, then flatten the result using reduce.
const input = [
{
id: 1,
data: {
user:
{
user1: 0,
user2: 1,
}
}
},
{
id: 2,
data: {
user:
{
user1: 0,
}
}
},
];
const output = input.map(obj => Object.keys(obj.data.user)
.map(user => ({
id: obj.id,
data: {
user, user_status: obj.data.user[user]
}
})
))
.reduce((a, b) => a.concat(b));
console.log(output)

Related

Retain array structure when filtering nested array

My brain froze with this advanced filtering. This task has exceeded my basic knowledge of filter, map etc.
Here I have an array with nested objects with array:
const DATA = [
{
title: 'Spongebob',
data: [
{ id: 1, name: 'Mr Crabs' },
{ id: 2, name: 'Sandy' }
]
},
{
title: 'Dragon Balls Z',
data: [
{ id: 1, name: 'GoKu' },
{ id: 2, name: 'Zamasu' }
]
}
];
You may have seen this sort of style if you've worked with React Native (RN). This question is not for RN. I need to perform a filter on the name property in the nested array and when I get a match, I must return the format as the DATA variable.
const handleFiltering = (value) => {
const _value = value.toLowerCase();
const results = DATA.map(o => {
return o.data.filter(o => o.name.toLowerCase().indexOf(_value) != -1)
});
console.log(results);
};
My limited knowledge of deep filtering returns the basic filtering for the data array but need to retain the structure for DATA. The expected results I'd expect:
// I'm now querying for "ZAMASU"
const handleFiltering = (value='ZAMA') => {
const _value = value.toLowerCase();
const results = DATA.map(o => {
return o.data.filter(o => o.name.toLowerCase().indexOf(_value) != -1)
});
// console.log(results) should now be
// [
// {
// title: 'Dragon Balls Z',
// data: [
// { id: 2, name: 'Zamasu' }
// ]
// }
// ];
};
What comes to mind is the use of {...DATA, something-here } but my brain has frozen as I need to get back the title property. How to achieve this, please?
Another solution would be first use filter to find only objects containing the name in data passed through the argument, subsequently mapping data.
Here is your adjusted filter method
const handleFiltering = (value) => {
const _value = value.toLowerCase();
const results = DATA.filter((obj) =>
obj.data.some((character) => character.name.toLowerCase() === _value)
).map((obj) => ({
title: obj.title,
data: obj.data.filter(
(character) => character.name.toLowerCase() === _value
),
}));
console.log(results);
};
You can use reduce method of array. First find out the object inside data array and then add that to accumulator array as new entry by preserving the original structure.
const DATA = [
{
title: 'Spongebob',
data: [
{ id: 1, name: 'Mr Crabs', where: 'tv' },
{ id: 2, name: 'Sandy' }
]
},
{
title: 'Dragon Balls Z',
data: [
{ id: 1, name: 'GoKu' },
{ id: 2, name: 'Zamasu' }
]
}
];
let handleFiltering = (value='tv') => {
return DATA.reduce((acc,d) => {
let obj = d.data.find(a => a.name?.toLowerCase().includes(value.toLowerCase())
|| a.where?.toLowerCase().includes(value.toLowerCase()));
obj ? acc.push({...d, data:[obj]}) : null;
return acc;
}, []);
}
let result = handleFiltering();
console.log(result);

Removing repeating elements in a array based on specific data [Discord.js]

I have a problem here that I can't deal with. There is little written about this on the internet. Well, when starting out, I need to make a function that will remove the duplicate data from the table, but the comparison of this data must be based on the data from the table object, below I will give an example because I do not know if I explained it well.
[
{
id: 1234,
user: {
nickname: 'a' <-
}
},
{
id: 1234,
user: {
nickname: 'b' <-
}
},
{
id: 1234,
user: {
nickname: 'a' <-
}
},
]
Data is to be compared according to user.nickname.
I tried to do it this way
array.filter((value, index) => array.indexOf (value.user.nickname) === index)
but all I got was a blank array
[]
If anyone can help, I will be grateful because I have this situation.
Your approach is wrong. Here's one way you can do it instead:
const mapOfNicknames = {};
array.forEach((e)=> {
const nick = e.user.nickname;
// check if nick already exists in map
if ( !mapOfNicknames[nick] ) {
mapOfNicknames[nick] = e;
}
});
// at this point, mapOfNicknames has a unique list
const uniqueArray = Object.keys(mapOfNicknames).map( k => mapOfNicknames[k] );
Using Array.filter as you try, should be a proper aproat:
const users = [
{
id: 1234,
user: {
nickname: "a",
},
},
{
id: 1234,
user: {
nickname: "b",
},
},
{
id: 1234,
user: {
nickname: "a",
},
},
];
let filteruniquebyUserName = users.filter(
(user, index, users) => users.findIndex((compareUser) => compareUser.user.nickname === user.user.nickname) === index
);
console.log(filteruniquebyUserName);
See: How to remove all duplicates from an array of objects?
Another way a little more extended but easier to understand:
const data = [
{
id: 1234,
user: {
nickname: "a",
},
},
{
id: 1234,
user: {
nickname: "b",
},
},
{
id: 1234,
user: {
nickname: "b",
},
},
{
id: 1234,
user: {
nickname: "a",
},
},
];
let elementRepeated = [];
let filteruniquebyUserName = data.filter((user, index, data) => {
if(elementRepeated.includes(user.user.nickname)) return;
const numberOfRepeatUser = data.filter(element => element.user.nickname === user.user.nickname).length;
if(numberOfRepeatUser > 1) elementRepeated.push(user.user.nickname);
return user
});
console.log(filteruniquebyUserName);
Apparently you can't do an indexOf check in a nested object like you are doing it right now. See: Javascript indexOf on an array of objects

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