Copy array of objects and make changes without modifying original array - javascript

I have an array of objects. I would like to deep copy the array of objects and make some changes to each object. I want to do this without modifying the original array or original objects that were in that array.
This is the way I have done it. However, being new to JavaScript I want to make sure this is a good approach.
Is there a better way to do this?
const users =
[
{
id : 1,
name : 'Jack',
approved : false
},
{
id : 2,
name : 'Bill',
approved : true
},
{
id : 3,
name : 'Rick',
approved : false
},
{
id : 4,
name : 'Rick',
approved : true
}
];
const users2 =
users
.map(
(u) =>
{
return Object.assign({}, u);
}
)
.map(
(u) =>
{
u.approved = true;
return u;
}
);
console.log('New users2 array of objects:')
console.log(users2);
console.log('This was original users array is untouched:')
console.log(users);
Output:
New users2 array of objects:
[ { id: 1, name: 'Jack', approved: true },
{ id: 2, name: 'Bill', approved: true },
{ id: 3, name: 'Rick', approved: true },
{ id: 4, name: 'Rick', approved: true } ]
This was original users array is untouched:
[ { id: 1, name: 'Jack', approved: false },
{ id: 2, name: 'Bill', approved: true },
{ id: 3, name: 'Rick', approved: false },
{ id: 4, name: 'Rick', approved: true } ]

For a single pass, you could use Object.assign with the changed property as well.
const users = [{ id: 1, name: 'Jack', approved: false }, { id: 2, name: 'Bill', approved: true }, { id: 3, name: 'Rick', approved: false }, { id: 4, name: 'Rick', approved: true }];
const users2 = users.map(u => Object.assign({}, u, { approved: true }));
console.log(users2);
console.log(users);
.as-console-wrapper { max-height: 100% !important; top: 0; }
UPDATE with spreading properties.
const users = [{ id: 1, name: 'Jack', approved: false }, { id: 2, name: 'Bill', approved: true }, { id: 3, name: 'Rick', approved: false }, { id: 4, name: 'Rick', approved: true }];
const users2 = users.map(u => ({ ...u, approved: true }));
console.log(users2);
console.log(users);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Yes that looks good. You could also perform the modification when you are cloning, in order to avoid mapping over the array twice.
const users2 = users.map((u) => {
const copiedUser = Object.assign({}, u);
copiedUser.approved = true;
return copiedUser;
});

I prefer JSON.stringify and JSON.parse
var users = [ { id: 1, name: 'Jack', approved: false },
{ id: 2, name: 'Bill', approved: true },
{ id: 3, name: 'Rick', approved: false },
{ id: 4, name: 'Rick', approved: true } ];
// user2 will be copy of array users without reference
var users2 = JSON.parse(JSON.stringify(users));

There are a few ways to copy a array in javascript, i believed that the most used are:
slice()
Array.from()
The slice function will return a portion (or all content) of a given array as a new array, based at a begin and end index (begin and end index are optional):
const a = [1,2,3,4,5,6,7,8,9]
/*
* Only begin index
*/
const b = a.slice(2)
console.log(b) //Will Print [3,4,5,6,7,8,9]
/*
* Begin index and end index
*/
const c = a.slice(5,8)
console.log(c) //Will Print [6,7,8]
/*
* No indexes provided
*/
const d = a.slice()
console.log(d) //Will print [1,2,3,4,5,6,7,8,9]
Array.from() is a function that will create a new array from an array-like or iterable parameters.
const a = Array.from('bar');
console.log(a) //Will Print ["b","a","r"]
const b = ["for","bar"];
const c = Array.from(b);
console.log(c) //Will print ["for","bar"]
More about slice
More about Array.from()

In React JS I tried most ways of copying and modifying the newly copied array but it still modified the original array. The methods I tried are slice Array.from for loop spread operator, all this ended up modifying the original array.
The solution was, stringify the original array and assign it to a new variable, then parse the variable to get a fresh copy without reference as stated by #Roman Yakoviv above.

Related

Compare and update two arrays without losing mutated data

I have an array of objects contains data of persons
const oldArr = [
{
id: 1,
name: 'Alex',
},
{
id: 2,
name: 'John',
},
{
id: 3,
name: 'Jack',
}
]
then I add data to this array to each element where I end up with new key called money with value of 20 as the following
oldArr.map((el, index) => el.money = 20)
and the array becomes like this
...
{
id: 2,
name: 'John',
money: 20
},
...
Now, I have a new array with new data (new person) but missing the money I have added before. (careful person with id 2 is not there)
const newArr = [
{
id: 1,
name: 'Alex',
},
{
id: 3,
name: 'Jack',
},
{
id: 4,
name: 'Chris',
},
]
I want to update the old array with new data but also keep the mutated data, and I want the result to end up like this:
const result = [
{
id: 1,
name: 'Alex',
money: 20
},
{
id: 3,
name: 'Jack',
money: 20
},
{
id: 4,
name: 'Chris',
},
]
Thanks for the help.
Just a note: map creates a whole new array, it doesn't make sense to use it for just mutating the contents. Use forEach or just a regular for loop instead.
oldArr.forEach((el) => (el.money = 20));
The following will give you the intended result:
const result = newArr.map(
(newEl) => oldArr.find((el) => el.id === newEl.id) || newEl
);
The OR operator || returns the second argument if the first is falsey.
You can optimize this by mapping items by id instead of brute force searching the old array.
const idMap = new Map();
oldArr.forEach((el) => {
el.money = 20;
idMap.set(el.id, el);
});
const result = newArr.map((newEl) => idMap.get(newEl.id) || newEl);
Stackblitz: https://stackblitz.com/edit/js-f3sw8w?file=index.js
If I getted it clear you are just trying to iterate throw the items of array generating a new array with the property "money" added to each one.
If so the map is the best option, just assign it to a new variable and change the item before return the element like bellow.
const oldArr = [
{
id: 1,
name: "Alex"
},
{
id: 2,
name: "John"
},
{
id: 3,
name: "Jack"
}
];
const newArr = oldArr.map((el) => {
el.money = "20";
return el;
});
console.log(oldArr);
console.log(newArr);
In this way you'll be able to keep both arrays.
If wasn't this, pls let me know.
Just merge the objects:
const result = oldArr.map((person) => ({
...person,
...newArr.find((cur) => cur.id === person.id),
}));

How do I exclude null values when merging objects by key?

Lets say I have an array of objects in Javascript:
id: 1,
name: 'Snowy',
},
{
id: 2,
name: 'Quacky',
age: 13
},
{
id: 3,
name: 'Snowy',
age: 1,
},
{
name: null
}
]
I have created a function to arrange them by a key:
const filter =
(pets, key) =>
_(pets)
.filter(pets => _.has(pets, key))
.groupBy(key)
.value()
Although, the output of this function when called persists the null entry:
{
Snowy: [ { id: 1, name: 'Snowy' }, { id: 3, name: 'Snowy', age: 1 } ],
Quacky: [ { id: 2, name: 'Quacky', age: 13 } ],
null: [ { name: null } ]
}
Is there I way I can filter out any of the null values here?
You could try by changing the method you use in in your predicate function from _.has to _.get.
So, instead of checking if a path/ key exist in an object, you instead check the value of a path/ key within an object it its null or not.
note: you might also want to check for falsy values here (such as undefined) instead of just null, since _.get returns undefined if the path/ key does not exist
const pets = [{
id: 1,
name: 'Snowy',
},
{
id: 2,
name: 'Quacky',
age: 13
},
{
id: 3,
name: 'Snowy',
age: 1,
},
{
name: null
}
];
const filter =
(pets, key) =>
_(pets)
.filter(pets => _.get(pets, key) !== null)
.groupBy(key)
.value();
console.log(filter(pets, 'name'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
Why not first filter the array for objects that have a truthy name? For example someArray.filter(a => a.name)...group()...

Comparing two Objects by id and creating the new one

So I have two objects with this structure:
const obj1 = { data:
[ {
id: 1,
name: 'Linda'
},
{
id: 2,
name: 'Mark'
}
];
const obj2 = [
{
id: 1,
salary: "2000, 60 USD"
},
undefined
],
[
{
id: 2,
salary: "4000, 50 USD"
},
undefined
]
I need to make a function to combine both of these into one object, based on id.
So the final results would be:
const finalObj = { data:
[ {
id: 1,
name: 'Linda',
salary: "2000, 60 USD"
},
{
id: 2,
name: 'Mark',
salary: "4000, 50 USD"
}
];
I have checked other questions, but could not find anything that would help. It can be done with lodash afaik, but don't know how.
I have tried the following:
finalObj = obj1.data.map(x => {
return {
...x,
...obj2
}
But it didn't map correctly.
Thanks.
EDIT: Updated obj2 response.
You can array#concat both your array and then using array#reduce and an object lookup with id, merge your objects. Then return all the values from this object.
const obj1 = { data: [{ id: 1, name: 'Linda' }, { id: 2, name: 'Mark' }]},
obj2 = { data: [{ id: 1, salary: "2000, 60 USD"}, { id: 2, salary: "4000, 50 USD"}]},
result = Object.values(obj1.data.concat(obj2.data).reduce((r,o) => {
r[o.id] = r[o.id] || {};
r[o.id] = {...r[o.id], ...o};
return r;
},{}));
console.log(result);
You could take a Map for collecting all properties of the same id in an object. Later get the values of the map.
const join = o => o && map.set(o.id, Object.assign(map.get(o.id) || {}, o));
var obj1 = { data: [{ id: 1, name: 'Linda' }, { id: 2, name: 'Mark' } ]},
obj2 = [{ id: 1, salary: "2000, 60 USD" }, undefined, { id: 2, salary: "4000, 50 USD" }, undefined],
map = new Map,
result;
obj1.data.forEach(join);
obj2.forEach(join);
result = { data: Array.from(map.values()) };
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Another way
const res = {
...obj1, // take the initial object
data: obj1.data.map(item => ({ // rewrite data property by iterate each item
// and merge item with corresponding
// object from obj2
...item, // take the item object
...obj2.find(({ id }) => id === item.id) // find corresponding object
}))
};
Here is an approach which would combine the objects and not overwrite the properties but only add the ones that are missing as well as avoid the undefined etc:
const names = {data: [{ id: 1, name: 'Linda' },{ id: 2, name: 'Mark' }]}
const salaries = [{ id: 1, salary: "2000, 60 USD" }, undefined]
var result = _.mapValues(names.data, x => {
let hit = _.find(salaries, y => y ? y.id === x.id : null)
return hit ? _.defaults(x, hit) : x
})
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
We are using mapValues to get to the values of names.data and look through them and for each of them get a hit in the salaries. If the hit exists we default the props of the hit with the current data object and return. Hope this helps.

How can I optimally group a list of objects by their sub object?

I'm trying to group some JavasScript objects by their shared similar object. I can do this effortlessly in Ruby, but for the life of my I (somewhat embarrassingly) can't figure this out in JS in linear time. JS doesn't seem to allow object literals as keys, at least for the purposes of reducing.
I have data shaped like this, as a result from a GraphQL query:
[
{
id: 1,
name: 'Bob',
room: {
id: 5,
name: 'Kitchen'
}
},
{
id: 3,
name: 'Sheila',
room: {
id: 5,
name: 'Kitchen'
}
},
{
id: 2,
name: 'Tom',
room: {
id: 3,
name: 'Bathroom'
}
}
]
In the UI, we're going to display the objects by the room they're in. We need to keep a reference to the room itself, otherwise we'd just sort by a room property.
What I'm trying to do is reshape the data into something like this:
{
{id: 5, name: 'Kitchen'}: [{id: 1, name: 'Bob'}, {id: 3, name: 'Sheila'}],
{id: 3, name: 'Bathroom'}: [{id: 2, name: 'Tom'}]
}
As you can see, the people are grouped together by the room they're in.
It could also be shaped like this...
[
{ room: {id: 5, name: 'Kitchen'}, people: [{id: 1, name: 'Bob', ...}] },
{ room: {id: 3, name: 'Bathroom', people: [{id: 2, name: 'Tom'}]
]
However it comes out, we just need the people grouped by the rooms in linear time.
I've tried lodash's groupBy, using both map and reduce, just doing for loops that put the list together, etc. I'm stumped because without being able to use an object literal (the room) as a hash index, I don't know how to efficiently group the outer objects by the inner objects.
Any help is greatly appreciated.
Update: adding clarity about trying to do it with linear time complexity - the most efficient equivalent of this Ruby code:
h = Hash.new { |h, k| h[k] = [] }
value.each_with_object(h) { |v, m| m[v[:room]] << v }
You can solve this using lodash#groupBy and lodash#map to gather and transform each group. Additionally, we use lodash#omit to remove the room object from each person from the people array.
var result = _(data)
.groupBy('room.id')
.map(people => ({
room: { ...people[0].room },
people: _.map(people, person => _.omit(person, 'room'))
})).value();
var data = [
{
id: 1,
name: 'Bob',
room: {
id: 5,
name: 'Kitchen'
}
},
{
id: 3,
name: 'Sheila',
room: {
id: 5,
name: 'Kitchen'
}
},
{
id: 2,
name: 'Tom',
room: {
id: 3,
name: 'Bathroom'
}
}
];
var result = _(data)
.groupBy('room.id')
.map(people => ({
// make sure to create a new room object reference
// to avoid mutability
room: { ...people[0].room },
people: _.map(people, person => _.omit(person, 'room'))
})).value();
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
You can use reduce to create an object of people indexed by rooms and then get that object's values, no library needed:
const input=[{id:1,name:'Bob',room:{id:5,name:'Kitchen'}},{id:3,name:'Sheila',room:{id:5,name:'Kitchen'}},{id:2,name:'Tom',room:{id:3,name:'Bathroom'}}]
const output = Object.values(
input.reduce((a, { id, name, room }) => {
const roomName = room.name;
if (!a[roomName]) a[roomName] = { room, people: [] };
a[roomName].people.push({ id, name });
return a;
}, {})
);
console.log(output);
Objects like
{id: 5, name: 'Kitchen'}: [{id: 1, name: 'Bob'}, {id: 3, name: 'Sheila'}],
in your question can't be properties like that unless the structure is a Map. Ordinary Javascript objects can only have string (/ number) properties.
One alternative is to use reduce in order to groupBy the rooms.
const input = [{
id: 1,
name: 'Bob',
room: {
id: 5,
name: 'Kitchen'
}
},
{
id: 3,
name: 'Sheila',
room: {
id: 5,
name: 'Kitchen'
}
},
{
id: 2,
name: 'Tom',
room: {
id: 3,
name: 'Bathroom'
}
}
];
const res = input
.map(person => ({
person: {
id: person.id,
name: person.name
},
room: person.room
}))
.reduce((rooms, person) => {
const room = rooms.find(room => room.id === person.room.id) ||
{ room: person.room };
const idx = rooms.indexOf(room);
room.people = room.people ?
[...room.people, person.person] :
[person.person];
return Object.assign(rooms, {
[idx === -1 ? rooms.length : idx]: room
});
}, []);
console.log(res);

Lodash check if any object in array contains id match from another array

I have one user array like:
var users = [{
id: 1,
name: 'ABC',
isDisplay: true
}, {
id: 2,
name: 'XYZ',
isDisplay: true
}, {
id: 3,
name: 'JKL',
isDisplay: true
}];
And another array selectedUsers which contains some object from above array like:
var selectedUsers = [{
id: 1,
name: 'ABC'
},
{
id: 3,
name: 'JKL'
}];
Not with lodash, i want to indentify which object are present in second array with matching its ID.
_.each(users, (_u) => {
if(selectedUsers.includes(_u)) {
_u.isDisplay = false;
} else {
_u.isDisplay = true;
}
});
I have tried to match whole object with includes but it dint work, because i am using angularjs, so angular put some $$hashkey with object, so it will not match, Is there any other way to do this.
var users = [{
id: 1,
name: 'ABC',
isDisplay: true
}, {
id: 2,
name: 'XYZ',
isDisplay: true
}, {
id: 3,
name: 'JKL',
isDisplay: true
}];
var selectedUsers = [{
id: 1,
name: 'ABC'
},
{
id: 3,
name: 'JKL'
}];
var intersection = _.intersectionBy(users, selectedUsers, 'id');
console.log(intersection);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.13.1/lodash.js"></script>
Create a Set of selected ids (selectedUsersIds). Iterate the users array with Array#forEach, assign the value of isDisplay by checking if the id exists in the selectedUsersIds set:
const users = [{"id":1,"name":"ABC","isDisplay":true},{"id":2,"name":"XYZ","isDisplay":true},{"id":3,"name":"JKL","isDisplay":true}];
const selectedUsers = [{"id":1,"name":"ABC"},{"id":3,"name":"JKL"}];
const selectedUsersIds = new Set(selectedUsers.map(({ id }) => id));
users.forEach((u) => u.isDisplay = selectedUsersIds.has(u.id));
console.log(users);

Categories

Resources