I have noticed that invoking .map() without assigning it to a variable makes it return the whole array instead of only the changed properties:
const employees = [{
name: "John Doe",
age: 41,
occupation: "NYPD",
killCount: 32,
},
{
name: "Sarah Smith",
age: 26,
occupation: "LAPD",
killCount: 12,
},
{
name: "Robert Downey Jr.",
age: 48,
occupation: "Iron Man",
killCount: 653,
},
]
const workers = employees.concat();
workers.map(employee =>
employee.occupation == "Iron Man" ? employee.occupation = "Philantropist" : employee.occupation
);
console.log(employees);
But considering that .concat() created a copy of the original array and assigned it into workers, why does employees get mutated as well?
This is happening because your objects within the array are still being referenced by same pointers. (your array is still referring to the same objects in memory). Also, Array.prototype.map() always returns an array and it's result should be assigned to a variable as it doesn't do in-place mapping. As you are changing the object's properties within your map method, you should consider using .forEach() instead, to modify the properties of the object within the copied employees array. To make a copy of your employees array you can use the following:
const workers = JSON.parse(JSON.stringify(employees));
See example below:
const employees = [
{
name: "John Doe",
age: 41,
occupation: "NYPD",
killCount: 32,
},
{
name: "Sarah Smith",
age: 26,
occupation: "LAPD",
killCount: 12,
},
{
name: "Robert Downey Jr.",
age: 48,
occupation: "Iron Man",
killCount: 653,
},
]
const workers = JSON.parse(JSON.stringify(employees));
workers.forEach(emp => {
if(emp.occupation == "Iron Man") emp.occupation = "Philantropist";
});
console.log("--Employees--")
console.log(employees);
console.log("\n--Workers--");
console.log(workers);
Note: If your data has any methods within it you need to use another method to deep copy
Problem analysis
workers = workers.map(employee =>
employee.occupation == "Iron Man" ? (employee.occupation = "Philantropist", employee) : (employee.occupation, employee)
);
[...] why does employees get mutated as well?
array.map() calls the passed function with each element from the array and returns a new array containing values returned by that function.
Your function just returns the result of the expression
element.baz = condition ? foo : bar;
which, depending on the condition, will
evaluate to foo or bar
assign that result to baz and
return that result
Further (expression1, expression2) will evaluate both expressions and return expression2 (see the comma operator).
So, although you return employee in both cases, you modify the original object with the left expression (expression 1).
Possible solution
You might want to create a new object using Object.assign()
array.map((employee) => Object.assign({ }, employee))
instead of using that array.concat() "trick". Using that mapping, you not only create a new array but also new objects with their attributes copied. Though this would not "deep copy" nested objects like { foo: { ... } } - the object accessible via the property foo would still be the same reference as the original. In such cases you might want to take a look on deep copying modules mentioned in the other answers.
The array references change but the copied array still reference the original objects in the original array. So any change in the objects in the array are reflected across all copies of the array. Unless you do a deep copy of the array, there is a chance that the some changes in the inner objects get reflected across each copy
What is the difference between a deep copy and a shallow copy?
Deep copies can be made in several ways. This post discusses specifically that: What is the most efficient way to deep clone an object in JavaScript?
map builds up a new array from the values returned from the callback, which caj easily be used to clone the objects in the array:
const workers = employees.map(employee => ({
...employee, // take everything from employee
occupation: employee.ocupation == "Iron Man" ? "Philantropist" : employee.occupation
}));
Or you could deep clone the array, then mutate it with a simple for:
const workers = JSON.parse(JSON.stringify(workers));
for(const worker of workers)
if(worker.ocupation == "Iron Man")
worker.ocupation = "Philantropist";
Related
in my object
let obj = {
name:["Harry","Scren","Docis","Altab"],
age:[32,44,22,55]
}
I need to write down a self executing anonymous function in object that, when i call any member of the object it will execute that self executing anonymous function and it will check whether the length of both array (name and age) are equal or not, If not equal then throw an error .....I need to have something like
let obj = {
name:["Harry","Scren","Docis","Altab"],
age:[32,44,22,55],
(function(){
if(name.length != age.length){
throw new Error('both name and age 's length are not equal')
}
}()) // But this is not possible to create in object I have just showed down what I wanted to create
}
How to create something like that in javascript object ??
I'm not sure, but I think that's not possible...
But let me ask a question: why you have an obj containing two arrays? Hasn't more sense having an array of the obj which each element of the array contains one name and one age? I'll show you
const people = [
{ name: "Pearson 1", age: 20 },
{ name: "Pearson 2", age: 20 },
{ name: "Pearson 3", age: 20 },
{ name: "Pearson 4", age: 20 },
]
So you do not have to check the length since every obj has its own name and age field.
I have a nested object like this
info ={
"id-1":
{
name: Jane,
age: 35,
experience: "7+",
position: manager,
},
"id-2":
{
age: 38,
name: John,
position: manager,
experience: "9+",
},
"id-3":
{
age: 42,
experience: "12+",
position: manager,
name: Max,
}
and I have a string
let myString ="name, age,position, experience"
I need to access keys in objects (name, age, position, experience) and sort them according to this string, so keys in the object would be in the same order as in myString, like this:
"id-1":
{
name: Jane,
age: 35,
position: manager,
experience: "7+",
},
How can I access keys in the nested objects and sort them in order? When I try to use Object.keys(info) it returns id-1, id-2, id-3 not name, age, position, experience. Please help, I can't seem to figure out a way.
First, most of the values of your keys are invalid. For example 7+ isn't a string, number or boolean and words like manager and John will be treated like variables if they don't have quotes around them. So your data needs to be fixed up.
Next, instead of wrapping the objects in an object that only has sequential key names (dashes are illegal syntax in key names unless the key names are quoted by the way), just place all the objects in an Array.
Then, just loop over the objects in the array and manually extract the individual key values you want in the order you want them. There is no need to think about re-sequencing the order that they are stored in.
let info = [
{name: "Jane", age: "35", experience: "7+", position: "manager" },
{age: "38", name: "John", position: "manager", experience: "9+"},
{age: "42", experience: "12+", position: "manager", name: "Max"}
];
let keyNames = ["name", "age", "position", "experience"];
// Loop over the objects in the array
info.forEach(function(obj){
let output = ""; // Will hold the output for one object at a time
// Loop over the key names in the array so we go in the desired order
keyNames.forEach(function(key){
// Build up the string with they key name and the key value
output += key + ": " + obj[key] + " ";
});
console.log(output); // Write out the string for the object
});
const monsters = {
'1': {
name: 'godzilla',
age: 250000000
},
'2': {
Name: 'manticore',
age: 21
}
}
I learn JavaScript from Codecademy, What does this code mean?
Is this two dimensional array? If not, what is it?
The data structure you are showing in your code example is not an array at all, it is an object. Arrays are defined using square brackets ([]) and their keys (indices) are not explicitly declared but rather assigned automatically.
So if you wrote your code like this, for example, you would have an array containing objects:
const monsters = [
{
name: 'godzilla',
age: 250000000
},
{
name: 'manticore',
age: 21
}
]
…so you could access the values by their array index, like so.
monsters[0].name; // godzilla
monsters[1].name; // manticore
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);
I'm having a similar need to a previous post on this topic: needing to order and sort an object without losing the keys. However, I have an object of objects:
var o = {
123: { field: "science", name: "zed" },
234: { field: "tech", name: "sara" },
672: { field: "arts", name: "jon" }
}
_.fromPairs(_.sortBy(_.toPairs(o),function(a){ return a[1] }).reverse())
The above uses the lodash solution mentioned the other topic - I'm however not getting any consistency of results (tho am retaining the key!)
Any help would be appreciated.
I’m not clear on what value you intended to sort against above, since objects are incomparable. Let’s say you wanted to sort them by the name field, though — you would find that it still didn’t work.
First some background:
Historically, object properties were considered unordered in ES. The sequence that keys would be enumerated (e.g. by for ... in or Object.keys()) was implementation-specific.
However enumeration order became a specified behavior in ES2015.
That enumeration order codifies what most engines were already doing:
Keys which are integers are enumerated first, from lowest to highest.
String keys are enumerated in the order of assignment.
Symbol keys are enumerated in the order of assignment.
If you need to use integers as ordered keys, you’ll require a different data structure. Map is appropriate:
const o = {
123: { field: "science", name: "zed" },
234: { field: "tech", name: "sara" },
672: { field: "arts", name: "jon" }
}
const { compare } = new Intl.Collator();
const res = new Map(Object
.entries(o)
.sort(([ , a ], [ , b ]) => compare(a.name, b.name))
);
console.log([ ...res ]);
If you need to worry about old IE, an array of key-value entries would also suffice.