What is the best approach to clone objects properties when using map()? - javascript

I want to add a new property (contactDetails.countryName) and assign a value to a nested object stored in an array called users using the function map().
I've recently learned that I should use the spread operator (...) and then create / assign the new property in order to avoid mutating my original array of objects so I've developed 2 different implementations for this but I'm not really confident I'm following the best practices to accomplish I want to regarding the semantic and performance.
What would be the best approach to accomplish what I want to do in your opinion?
const countries = [
{ id: 3, countryName : "UK" },
{ id: 4, countryName : "Spain" },
{ id: 6, countryName : "Germany"}
];
const users = [
{ id : 1,
name: "Douglas Camp",
dateOfBirth: "23-06-1984",
contactDetails:
{
country: 3,
phone: "7373724997"
}
},
{
id : 2,
name: "Martin Stein",
dateOfBirth: "19-08-1992",
contactDetails:
{
country: 6,
phone: "3334343434"
}
},
];
const usersData = users.map(user=> {
// Version 1 : using spreading operator twice
const newUser = {
...user,
contactDetails: {
...user.contactDetails,
countryName: countries.find(c=> c.id == user.contactDetails.country).countryName
}
};
return newUser;
});
// Version 2: copying the original object property and using spread operator only for cloning the nested object properties
const newUser = {
id: user.id,
name: user.name,
dateOfBirth: user.dateOfBirth,
contactDetails: {
...user.contactDetails,
countryName: countries.find(c=> c.id == user.contactDetails.country).countryName
}
};
console.log(users);
console.log(usersData);

Here is an approach you can consider:
First of all I would Array.reduce the countries to a Map so you can get them via key/value or in this case by countries.get(key) and avoid filtering that array every time.
You can map through the users and for each one create a new object. In this case I call them accounts.
You can also consider using Object.assign
Note that both ... operator and Object.assign are shallow clone approaches. They do not recursively clone the nested objects/children. For that you can use JSON.stringify and JSON.parse etc.
let countries = [
{ id: 3, countryName : "UK" },
{ id: 4, countryName : "Spain" },
{ id: 6, countryName : "Germany"}
].reduce((r,{id, countryName}) => (r.set(id, countryName), r), new Map()) // reduce with Map
let users = [ { id : 1, name: "Douglas Camp", dateOfBirth: "23-06-1984", contactDetails: { country: 3, phone: "7373724997" } }, { id : 2, name: "Martin Stein", dateOfBirth: "19-08-1992", contactDetails: { country: 6, phone: "3334343434" } }, ];
let accounts = users.map(user => Object.assign({}, user, { // <-- map through
contactDetails: {
...user.contactDetails,
countryName: countries.get(user.contactDetails.country) // <-- get by key
}
}))
users[0].id = 2 // <-- modify users
users[0].contactDetails.phone = "00000"
console.log(users, accounts) // <-- no changes to accounts
Notice when we update the users[0].id and users[0].contactDetails.phone the accounts values did not update.

I normally use version 1, the spread operator twice. I also would consider checking out immer which allows you to do mutable updates on a cloned draft and handles merging it back for you.
const newUser = immer(user, draft => {
draft.contactDetails.countryName = countries.find(
c => c.id == user.contactDetails.country).countryName
)
})
Just edit the specific property you want and immer handles copying the rest of it.

Cloning and merging MapsSection
Just like Arrays, Maps can be cloned:
var original = new Map([
[1, 'one']
]);
var clone = new Map(original);
console.log(clone.get(1)); // one
console.log(original === clone); // false. Useful for shallow comparison

I personally like to use Version 1, as it makes your code much less redundant and easier to read. It also passes all the properties of 'user' down to newUser.

Related

How can I update some array entries to have multiple values?

I have an array as such:
arr = {
name: 1,
address: 1,
phone: 2,
email: 5,
};
I want to be able to add further information to this array, eg:
arr = {
name: 1 true,
address: 1 false,
phone: 2 true,
email: 5 true,
};
I've tried a few different things like:
arr.email[2] = true;
With no results (or errors).
Is there a way to do this? Or a better way of handling this issue?
I'm not entirely certain what you're going for here since you mention wanting an array ([]) but what you've shown in your question is an object ({}), but if I'm reading right you can accomplish this with an object where each key holds an array of values. That would look like:
const obj = {
name: [1],
address: [1],
phone: [2],
email: [5],
};
obj.email.push(true);
obj.email.push("whatever");
console.log(obj)
console.log(obj.email[1])
console.log(obj.email[2])
So obj is an object, but name, address, phone, and email are all arrays which you can extend as needed with array methods.
Original data structure doesn't allow you to add data the way you want. So you have to create the object with needed data structure at first. After this the original data should be moved to the new object and only after this you can add some new data.
newObj.name.push(origObj.name,true,"something1","something2");
newObj.address.push(origObj.address,false);
newObj.phone.push(origObj.phone,true);
newObj.email.push(origObj.email,true);
output
{
"name":[1,true,"something1","something2"],
"address":[1,false],
"phone":[2,true],
"email":[5,true]
}
original object
var origObj = {
name: 1,
address: 1,
phone: 2,
email: 5
};
new object
var newObj = {
name: [],
address: [],
phone:[],
email: []
};
You would need to assign a variable to your array and call a function...that way, each element could be classified (boolean, string, integer, etc.) For example,
const fruit = ['banana', 'apple', 'pear'];
console.log(fruit);

Trying to create array of strings to use in a destructuring function for removing object properties, only single string value works

I have an array of objects as part of a data response that I am grouping together using lodash's groupBy via each object's groupName key.
Some of the items that come back have a groupName value of null, undefined or an empty string and lodash creates separate groups for each of those values.
I combine all of the falsey groups into a single group name "Uncategorized" and attempt to remove the original falsey groups to only return "Uncategorized" and all other truthy groups.
The problem I'm running into is that I'm trying to use the rest operator to remove the original falsy objects with undefined, null, and empty string keys by assigning them to a variable like let groupKeysToRemove = ['undefined', 'null', ''] and then trying to remove them like let { [groupKeysToRemove]: removed, ...groups } = initialGroups; but it returns the same Object with nothing removed. I'm not sure if my syntax is wrong or what but I am stumped.
Code via sandbox:
const resources = [
{
groupName: undefined,
name: "color organizer"
},
{
groupName: null,
name: "Bart_Simpson_200px"
},
{
groupName: "Spread Sheets",
name: "Backflow"
},
{
groupName: "Spread Sheets",
name: "220px-Marge_Simpson"
},
{
groupName: "",
name: "212px-Homer_Simpson_2006"
},
{
groupName: "Spread Sheets",
name: "Product 6"
},
{
groupName: "Warranties",
name: "Warranty Bart Simpson"
},
{
groupName: "Warranties",
name: "Warranty Product 2"
},
{
groupName: "Warranties",
name: "Warranty Product 3"
}
];
let initialGroups = groupBy(resources, "groupName");
let uncategorizedGroups = [];
uncategorizedGroups.push(...initialGroups[undefined], ...initialGroups[null], ...initialGroups[""]);
const renameGroups = uncategorizedGroups.map((object) => {
object.groupName = "Uncategorized";
return object;
});
const renamedGroups = groupBy(renameGroups, "groupName");
console.log('RENAMED GROUPS: ', renamedGroups)
const groupKeysToRemove = "undefined"
let { [groupKeysToRemove]: removed, ...groups } = initialGroups;
groups = { ...groups, ...renamedGroups };
Think of the brackets syntax [] for the destructing operation as an index to a property of an object, not an array that you pass in. It's analogous to calling for example obj["a"] vs obj.a to access the a field on obj.
So knowing this, you need to pass in 3 arguments to extract the values that you want to remove. For null and undefined I had to put them in separate variables, it wasn't working when putting them directly in the brackets:
const nullKey = null;
const undefinedKey = undefined;
let {
[nullKey]: nullGroup,
[undefinedKey]: undefinedGroup,
[""]: emptyStringGroup,
...groups } = initialGroups;
groups = { ...groups, ...renamedGroups };
console.log("initialGroups: ", initialGroups);
console.log("GROUPS: ", groups);
console.log("null group", nullGroup)
console.log("undef group", undefinedGroup)
console.log("emptyStringGroup group", emptyStringGroup)

How to get JSON keys and add extra fields?

I'm trying to get the key of these json objects in order to create a new object with extra filed to create table headers in a React app. JSON data:
let example = [
{
id: 1,
city: 'New York',
},
{
id: 2,
city: 'Paris',
},
]
The function:
getKeys() {
return example.map((key) => {
return {
cityName: key, // gets the whole array
capital: false,
};
});
}
I tries Object.keys( example);, it returns integers; 0, 1.
How can I get the keys in this case? Thanks.
You are trying to map the keys for an array since example is an array. If the data are consistent throughout the array get the first element example[0] and do Object.keys().
So Object.keys(example[0])
There's no need to get the keys if you just want to add a property to the items in the array. I think there's a misunderstanding about .map, which gives you a single item/object in the array, not the keys.
Something like this perhaps?
let example = [{
id: 1,
city: 'New York',
}, {
id: 2,
city: 'Paris',
}];
const modifiedArray = function(arr) {
return arr.map(item => {
return {
id: item.id,
cityName: item.city,
capital: false,
};
})
}
const newArray = modifiedArray (example);
console.log(newArray )

json object from javascript nested array

I'm using a nested array with the following structure:
arr[0]["id"] = "example0";
arr[0]["name"] = "name0";
arr[1]["id"] = "example1";
arr[1]["name"] = "name1";
arr[2]["id"] = "example2";
arr[2]["name"] = "name2";
now I'm trying to get a nested Json Object from this array
arr{
{
id: example0,
name: name00,
},
{
id: example1,
name: name01,
},
{
id: example2,
name: name02,
}
}
I tought it would work with JSON.stringify(arr); but it doesen't :(
I would be really happy for a solution.
Thank you!
If you are starting out with an array that looks like this, where each subarray's first element is the id and the second element is the name:
const array = [["example0", "name00"], ["example1", "name01"], ["example2", "name02"]]
You first need to map it to an array of Objects.
const arrayOfObjects = array.map((el) => ({
id: el[0],
name: el[1]
}))
Then you can call JSON.stringify(arrayOfObjects) to get the JSON.
You need to make a valid array:
arr = [
{
id: 'example0',
name: 'name00',
},
{
id: 'example1',
name: 'name01',
},
{
id: 'example2',
name: 'name02',
}
];
console.log(JSON.stringify(arr));
Note that I am assigning the array to a variable here. Also, I use [] to create an array where your original code had {}.

Javascript arrays and objects: Get differences and merge them

So I have this scenario where I have a client-app which sends data (array of objects) to a server which then forwards the data to other clients connected to this server.
In the client-app, the data is constantly changing, meaning: Values change, new objects inside the array pop up, objects being removed, and so on ...
Now I want the other clients to always receive the latest data. And because I dont want the client-app to just push the whole new data to the server which then forwards the whole new data to the other clients, I decided to let the client-app only push the changes (using this library: https://www.npmjs.com/package/deep-object-diff).
The other clients then receive an array of objects with only the data that has actually changed and because they know the previous data array, I want them to "merge" the array of changes with the old data object.
My actual problem is the merging. I dont know how to properly do this. Especially if I have an array of objects without any key for the objects.
So my data looks something like this:
let data = [
{
name: 'Peter',
age: 26,
sID: 546589995544
},
{
name: 'John',
age: 33,
sID: 554589525469
}
];
Actually there's much more but well, thats the structure.
So if the diff library says, this are the changes:
let changes = {
{
age: 34,
sID: 554589525469
}
};
(notice that I now have an object of objects, not an array of objects. Thats what the diff-library returns)
I want the merged object to be
[
{
name: 'Peter',
age: 26,
sID: 546589995544
},
{
name: 'John',
age: 34,
sID: 554589525469
}
];
(John is now one year older)
So I totally believe that this would be much easier if I had a key to the objects as an identifier, but still I think there has to be a solution for exactly this scenario. And as you can see, the sID property could act as an identifier, its just not a key.
I would apprectiate if someone could point out how to do it in both cases (with and without a specific key for the objects)
You can use .find() to find the object within the array where values should be changed, Object.assign() to set the values
let data = [{
name: 'Peter',
age: 26,
sID: 546589995544
},
{
name: 'John',
age: 33,
sID: 554589525469
}
];
let changes = [{
age: 34,
sID: 554589525469
}];
for (let prop of changes) {
let {sID} = prop;
Object.assign(data.find(({sID: id}) => id === sID), prop)
}
console.log(data);
You could use a sId Map for fast lookup:
const byId = new Map( data.map( el => [el.sID, el]));
Then for every change we can find if the obj already exists, if not we add it, if yes we mutate:
changes.forEach(change => {
const res = byId.get( change.sID );
if( res ){
Object.assign( res, change);
}else{
data.push(change);
byId.set( change.sID, change);
}
});
Using lodash, you can accomplish this with unionBy :
const newData = _.unionBy(changes, data, 'sID'); // values from changes will be picked
This will pick objects from both the arrays based on sID and combine them into a single array.
If your changes data is object of objects , you can use Object.values to loop data value and merge same id data by Object.assign
let data = [
{
name: 'Peter',
age: 26,
sID: 546589995544
},
{
name: 'John',
age: 33,
sID: 554589525469
}
];
let changes = {
0:
{
age: 34,
sID: 554589525469
}
};
data.filter((idx,i)=>
Object.values(changes).forEach((index)=>
(index.sID == idx.sID) ? Object.assign(data[i],index) : null
)
);
console.log(data);

Categories

Resources