Problems with JavaScript object destructuring - javascript

let pppp = {
name: "duanxiao",
age: 1,
job: {
title: "~~~"
}
};
let ppppCopy = {};
({
name: ppppCopy.name,
age: ppppCopy.age,
job: ppppCopy.job
} = pppp);
pppp.job.title = "Hacker";
console.log(pppp);
console.log(ppppCopy);
The output values ​​are the same.
Why modifying the value of one object, the other object will also be modified?
Whenever I modify the value of one object, the value of the other object is also modified.

Because you pppp and ppppCopy holds the same reference of job property. Changing at one location will impact another. You can achieve your intended outcome with below code using ES6 spread operator,
let pppp = {
name: "duanxiao",
age: 1,
job: {
title: "~~~"
}
};
const ppppCopy = {
...pppp,
job: { ...pppp.job },
};
With this, updating pppp.job.title will not impact ppppCopy.job.title.
You can also use the traditional way like JSON.parse(JSON.stringify(pppp)), but you need to be more cautious while using this approach as it strips down the function property

In JS, and many other languages, you have data types that store by value, like Number and other primitive types. Some data types stored by reference, like Arrays, Object.
By destructing pppp you just passing the reference to the inner job object, and not duplicating it, so technically its the same job object in pppp and ppppCopy.
Here I added a manipulation to a primitive, and you can see that there is a difference.
let pppp = {
name: "duanxiao",
age: 1,
job: {
title: "~~~"
}
};
let ppppCopy = {};
({
name: ppppCopy.name,
age: ppppCopy.age,
job: ppppCopy.job
} = pppp);
pppp.job.title = "Hacker";
pppp.age = 123;
console.log(pppp);
console.log(ppppCopy);
Here is another good answer related

The only way is to use JSON.parse(JSON.stringify(pppp));
name: "duanxiao",
age: 1,
job: {
title: "~~~"
}
};
let ppppCopy = JSON.parse(JSON.stringify(pppp));
pppp.job.title = "Hacker";
console.log(pppp);
console.log(ppppCopy);

Since objects are non-primitive data types javascript makes a reference of the original object when you make a copy using the assignment operator as you have done in your case. In order to avoid shallow copying you can use the spread operator i.e. let copy = { ...original} or you can use the assign method i.e. let copy = Object.assign({}, original) but both of these fail when you have a nested object. In order to deep copy a nested object you need to do it as Edoardo pointed out above but that will fail too when there is a function in your object. Ravindra's method can also be used, but it will be a hassle when you have multiple nested objects.
The best way to do it in my opinion is to use lodash _.cloneDeep() method. You can read more about it here

Related

Merging a json response into an already initialized object javascript

I have two different objects but they have some common properties. Both are filled with some values. I want to assign X object's values to override Y's property values if they have the same key in both object, else do nothing but y's empty prop keys should be remain.
I can achieve this purpose with some custom mapping operations but is there a simple way to do it in javascript?
var x = {
name: 'John',
addressInformation: null
}
var y = {
name: '',
paymentInformation: {
iban: '12313123',
cardNumber: '1231231231'
},
addressInformation: {
city: 'Berlin'
}
}
Merged object should be in my scenario:
y = {
name 'John',
paymentInformation: {
iban: '12313123',
cardNumber: '1231231231'
},
addressInformation: {
city: 'Berlin'
}
}
(I only shared the simplified version of that objects. There are many more properties)
Object.assign or spread operator will do the job.
const newObject = Object.assign(y, x);
const newObject = {
...y,
...x
}
Also, what aweebit said is important.
It is important to note that, whereas { ...y, ...x } leaves y intact and merges y and x into a new object which is assigned to the variable newObject, Object.assign(y, x) mutates y and returns it without creating a new object, so newObject === y evaluates to true after calling it.
I would post this as a comment to Baruch's answer but I do not have enough reputation yet, so your upvotes are appreciated :)

How to copy object to new variable

Let's say I have class called Person, which have firstName, lastName, and Array of addresses fields.
And in one of my angular components I am getting Person object to do some operation on them. But I would like to create two copies of this obj. To do that I am using Object.assign. But after that when I am manipulating firstName from firstCopyPerson all other objects are changed too.
How can I assign object to new variable without making reference to orginal object, but instead just creating new separate object?
mainPerson: Person;
firstCopyPerson: Person;
secondCopyPerson: Person;
ngOnInit() {
this.mainPerson = someCache.getPerson();
this.firstCopyPerson: Object.assign({}, this.mainPerson);
this.secondCopyPerson: Object.assign({}, this.mainPerson);
}
You can add this re-usable function so you can reuse this function everytime
const copyObject = (obj) => {
let result = {};
Object.entries(obj).forEach(([key, value]) => {
result[key] = value;
});
return result;
};
// Test the function
const obj1 = {name: 'notebook', price: 100};
objCopy = copyObject(obj1);
console.log(objCopy);
You can also use this way
const obj = {name: 'laptop', price: 100};
// This is the implementation line
let objCopy = {...obj};
console.log(objCopy);
whenever you use this code you are assigning a new reference to the object and both variables assigning to the same object so when you edit one of them the other will change too. you can solve this by destructing the object for example you can try this :
let personCopy = {...person}
Object.assign would only make a shallow copy of the object, that means all attributes are assigned to the same data objects.
You may want to read about deep copies which are the safe way to go.
Fenton already posted a brilliant answer to this, take a look at option 4 in his answer:
https://stackoverflow.com/a/28152032/8341158

Objection.js not proper return

i am not getting proper the return after insertgraph in objection.js
i am getting the result like :
[
User {
name: 'Santosh Devi',
city: 'Suratgarh',
number: '9898987458',
userroles: UserRoles { role_id: 2, user_id: 37 },
id: 37
}
]
where i want the result like :
[
{
name: 'Santosh Devi',
city: 'Suratgarh',
number: '9898987458',
userroles: { role_id: 2, user_id: 37 },
id: 37
}
]
There are few ways to get rid of the specific class references:
1. JSON.parse(JSON.stringify(result))
This will rebuild the object by first converting the whole object to a string (in JSON format), and then by doing the reverse -- creating a new object from a string. As this string format (JSON) does not store custom class information, it achieves your purpose. However, if your object has functions, symbols, then these will be omitted. Also Map and Set will become empty objects. For a more complete list of restrictions. See JSON.stringify
2. Deep Clone
There are several deep-clone functions out there, that may or may not do what you expect. Some will still try to maintain the original prototype references, so that it would not benefit you. You can find some here: How to Deep clone in javascript. For your case, this one would do the job:
function deepClone(obj, hash = new WeakMap()) {
if (Object(obj) !== obj) return obj; // primitives
if (hash.has(obj)) return hash.get(obj); // cyclic reference
const result = Array.isArray(obj) ? [] : {};
hash.set(obj, result);
return Object.assign(result, ...Object.keys(obj).map(
key => ({ [key]: deepClone(obj[key], hash) }) ));
}
You call it as newResult = deepClone(result).
The advantage here, is that it supports cyclic references, which JSON.stringify cannot handle. Also, there is no string conversion happening, which really is not necessary. You can extend this function to keep deal with some class instances that you like to stay that way. See how you can support Date, RegExp, Map, Set, ... in this answer. But don't do the "catch-all" line.
3. Change the prototype
With this strategy you mutate the result in-place.
function removeClasses(obj, hash = new WeakSet()) {
if (Object(obj) !== obj) return; // primitives
if (hash.has(obj)) return; // cyclic reference
hash.add(obj);
if (Array.isArray(obj)) Object.setPrototypeOf(obj, Array.prototype);
else Object.setPrototypeOf(obj, Object.prototype);
for (let value of Object.values(obj)) {
removeClasses(value, hash);
}
}
Call it as removeClasses(result), and afterwards result will have been "fixed". Again, this method does not use a conversion to string. As it does not create a new object either, it consumes less memory. But on the other hand you mutate an object, and some would advise against that.

Can you use a variable name to reference an Object without using eval()

So,
I know it is possible to create dynamic keys on an object.
const foo = 'bar';
const obj = {
[foo]: 'this is bar',
};
But is it possible to reference objects by a variable value. For instance if we have 3 objects:
const obj1 = {}
const obj2 = {}
const obj3 = {}
And an array which holds their names
const objects = ['obj1', 'obj2', 'obj3'];
Can we loop through those names and reference their objects. This won't work:
objects.forEach(obj => Object.assign(obj, { extended: true }));
But this will:
objects.forEach(obj => Object.assign(eval(obj), { extended: true }));
But the docs at Mozilla state "Do not ever use eval!"
The answer is that you can't.
In JS, there's no way to access or create variables dynamically, unless you eval (or the very similar Function constructor).
Although the variables created in the global scope with var will become properties of the global object and you can access them on it, that's also not recommended (due to the problems arisig with var, using reserved names, etc.), and works only in the global scope (which is even worse to mess with).
The best thing you can do is to place these variables into an object instead, and that way you can access them dynamically. JS variables aren't good for that.
So if I've understood you correctly, you have a list of objectKeys, and you want to produce an array of those objects. Would this work?
const objNames = ['obj1', 'obj2', 'obj3' ]
let objects = [];
for(let i = 0; i < objNames.length; i++){
let tempObj = {};
tempObj[objNames[i]] = {extended:true}
objects.push(tempObj);
}
If I understood your question correctly, you can do it like that.
const obj1 = {id: 1}
const obj2 = {id: 2}
const obj3 = {id: 3}
var objects = ['obj1', 'obj2', 'obj3'].map(element => [element]);
console.log('objects : ', objects)
no you can't (without eval()) and it's not a good idea because variable name can be modified for example if you use a minifier
I suggest to use an Array
const objects = [{}, {}, {}]
or, if name is important, an Object:
const objects = {
obj1: {},
obj2: {},
obj3: {},
};
so you can easily iterate it

Design pattern to check if a JavaScript object has changed

I get from the server a list of objects
[{name:'test01', age:10},{name:'test02', age:20},{name:'test03', age:30}]
I load them into html controls for the user to edit.
Then there is a button to bulk save the entire list back to the database.
Instead of sending the whole list I only want to send the subset of objects that were changed.
It can be any number of items in the array. I want to do something similar to frameworks like Angular that mark an object property like "pristine" when no change has been done to it. Then use that flag to only post to the server the items that are not "pristine", the ones that were modified.
Here is a function down below that will return an array/object of changed objects when supplied with an old array/object of objects and a new array of objects:
// intended to compare objects of identical shape; ideally static.
//
// any top-level key with a primitive value which exists in `previous` but not
// in `current` returns `undefined` while vice versa yields a diff.
//
// in general, the input type determines the output type. that is if `previous`
// and `current` are objects then an object is returned. if arrays then an array
// is returned, etc.
const getChanges = (previous, current) => {
if (isPrimitive(previous) && isPrimitive(current)) {
if (previous === current) {
return "";
}
return current;
}
if (isObject(previous) && isObject(current)) {
const diff = getChanges(Object.entries(previous), Object.entries(current));
return diff.reduce((merged, [key, value]) => {
return {
...merged,
[key]: value
}
}, {});
}
const changes = [];
if (JSON.stringify(previous) === JSON.stringify(current)) {
return changes;
}
for (let i = 0; i < current.length; i++) {
const item = current[i];
if (JSON.stringify(item) !== JSON.stringify(previous[i])) {
changes.push(item);
}
}
return changes;
};
For Example:
const arr1 = [1, 2, 3, 4]
const arr2 = [4, 4, 2, 4]
console.log(getChanges(arr1, arr2)) // [4,4,2]
const obj1 = {
foo: "bar",
baz: [
1, 2, 3
],
qux: {
hello: "world"
},
bingo: "name-o",
}
const obj2 = {
foo: "barx",
baz: [
1, 2, 3, 4
],
qux: {
hello: null
},
bingo: "name-o",
}
console.log(getChanges(obj1.foo, obj2.foo)) // barx
console.log(getChanges(obj1.bingo, obj2.bingo)) // ""
console.log(getChanges(obj1.baz, obj2.baz)) // [4]
console.log(getChanges(obj1, obj2)) // {foo:'barx',baz:[1,2,3,4],qux:{hello:null}}
const obj3 = [{ name: 'test01', age: 10 }, { name: 'test02', age: 20 }, { name: 'test03', age: 30 }]
const obj4 = [{ name: 'test01', age: 10 }, { name: 'test02', age: 20 }, { name: 'test03', age: 20 }]
console.log(getChanges(obj3, obj4)) // [{name:'test03', age:20}]
Utility functions used:
// not required for this example but aid readability of the main function
const typeOf = o => Object.prototype.toString.call(o);
const isObject = o => o !== null && !Array.isArray(o) && typeOf(o).split(" ")[1].slice(0, -1) === "Object";
const isPrimitive = o => {
switch (typeof o) {
case "object": {
return false;
}
case "function": {
return false;
}
default: {
return true;
}
}
};
You would simply have to export the full list of edited values client side, compare it with the old list, and then send the list of changes off to the server.
Hope this helps!
Here are a few ideas.
Use a framework. You spoke of Angular.
Use Proxies, though Internet Explorer has no support for it.
Instead of using classic properties, maybe use Object.defineProperty's set/get to achieve some kind of change tracking.
Use getter/setting functions to store data instead of properties: getName() and setName() for example. Though this the older way of doing what defineProperty now does.
Whenever you bind your data to your form elements, set a special property that indicates if the property has changed. Something like __hasChanged. Set to true if any property on the object changes.
The old school bruteforce way: keep your original list of data that came from the server, deep copy it into another list, bind your form controls to the new list, then when the user clicks submit, compare the objects in the original list to the objects in the new list, plucking out the changed ones as you go. Probably the easiest, but not necessarily the cleanest.
A different take on #6: Attach a special property to each object that always returns the original version of the object:
var myData = [{name: "Larry", age: 47}];
var dataWithCopyOfSelf = myData.map(function(data) {
Object.assign({}, data, { original: data });
});
// now bind your form to dataWithCopyOfSelf.
Of course, this solution assumes a few things: (1) that your objects are flat and simple since Object.assign() doesn't deep copy, (2) that your original data set will never be changed, and (3) that nothing ever touches the contents of original.
There are a multitude of solutions out there.
With ES6 we can use Proxy
to accomplish this task: intercept an Object write, and mark it as dirty.
Proxy allows to create a handler Object that can trap, manipulate, and than forward changes to the original target Object, basically allowing to reconfigure its behavior.
The trap we're going to adopt to intercept Object writes is the handler set().
At this point we can add a non-enumerable property flag like i.e: _isDirty using Object.defineProperty() to mark our Object as modified, dirty.
When using traps (in our case the handler's set()) no changes are applied nor reflected to the Objects, therefore we need to forward the argument values to the target Object using Reflect.set().
Finally, to retrieve the modified objects, filter() the Array with our proxy Objects in search of those having its own Property "_isDirty".
// From server:
const dataOrg = [
{id:1, name:'a', age:10},
{id:2, name:'b', age:20},
{id:3, name:'c', age:30}
];
// Mirror data from server to observable Proxies:
const data = dataOrg.map(ob => new Proxy(ob, {
set() {
Object.defineProperty(ob, "_isDirty", {value: true}); // Flag
return Reflect.set(...arguments); // Forward trapped args to ob
}
}));
// From now on, use proxied data. Let's change some values:
data[0].name = "Lorem";
data[0].age = 42;
data[2].age = 31;
// Collect modified data
const dataMod = data.filter(ob => ob.hasOwnProperty("_isDirty"));
// Test what we're about to send back to server:
console.log(JSON.stringify(dataMod, null, 2));
Without using .defineProperty()
If for some reason you don't feel comfortable into tapping into the original object adding extra properties as flags, you could instead populate immediately
the dataMod (array with modified Objects) with references:
const dataOrg = [
{id:1, name:'a', age:10},
{id:2, name:'b', age:20},
{id:3, name:'c', age:30}
];
// Prepare array to hold references to the modified Objects
const dataMod = [];
const data = dataOrg.map(ob => new Proxy(ob, {
set() {
if (dataMod.indexOf(ob) < 0) dataMod.push(ob); // Push reference
return Reflect.set(...arguments);
}
}));
data[0].name = "Lorem";
data[0].age = 42;
data[2].age = 31;
console.log(JSON.stringify(dataMod, null, 2));
Can I Use - Proxy (IE)
Proxy - handler.set()
Global Objects - Reflect
Reflect.set()
Object.defineProperty()
Object.hasOwnProperty()
Without having to get fancy with prototype properties you could simply store them in another array whenever your form control element detects a change
Something along the lines of:
var modified = [];
data.forEach(function(item){
var domNode = // whatever you use to match data to form control element
domNode.addEventListener('input',function(){
if(modified.indexOf(item) === -1){
modified.push(item);
}
});
});
Then send the modified array to server when it's time to save
Why not use Ember.js observable properties ? You can use the Ember.observer function to get and set changes in your data.
Ember.Object.extend({
valueObserver: Ember.observer('value', function(sender, key, value, rev) {
// Executes whenever the "value" property changes
// See the addObserver method for more information about the callback arguments
})
});
The Ember.object actually does a lot of heavy lifting for you.
Once you define your object, add an observer like so:
object.addObserver('propertyKey', targetObject, targetAction)
My idea is to sort object keys and convert object to be string to compare:
// use this function to sort keys, and save key=>value in an array
function objectSerilize(obj) {
let keys = Object.keys(obj)
let results = []
keys.sort((a, b) => a > b ? -1 : a < b ? 1 : 0)
keys.forEach(key => {
let value = obj[key]
if (typeof value === 'object') {
value = objectSerilize(value)
}
results.push({
key,
value,
})
})
return results
}
// use this function to compare
function compareObject(a, b) {
let aStr = JSON.stringify(objectSerilize(a))
let bStr = JSON.stringify(objectSerilize(b))
return aStr === bStr
}
This is what I think up.
It would be cleanest, I’d think to have the object emit an event when a property is added or removed or modified.
A simplistic implementation could involve an array with the object keys; whenever a setter or heck the constructor returns this, it first calls a static function returning a promise; resolving: map with changed values in the array: things added, things removed, or neither. So one could get(‘changed’) or so forth; returning an array.
Similarly every setter can emit an event with arguments for initial value and new value.
Assuming classes are used, you could easily have a static method in a parent generic class that can be called through its constructor and so really you could simplify most of this by passing the object either to itself, or to the parent through super(checkMeProperty).

Categories

Resources