How to copy json object without reference in vue? - javascript

In my component i have declarated some data like this:
data() {
return {
defaultValue: {json object with some structure},
activeValue: {}
...
And in component methods a make copy this value:
this.activeValue = this.defaultValue
But problem is, after change this.activeValue value a have changes in this.defaultValue too.
If i use Object.freeze(this.defaultValue) and trying change this.activeValue i have get error - object is not writable.
How i can make copy of data but without reference?

If you have simple object, quickest and easiest way is to just use JSON.parse and JSON.stringify;
const obj = {};
const objNoReference = JSON.parse(JSON.stringify(obj));

this.activeValue = { ...this.defaultValue }
Using an ES6 spread operator will help you to do a copy if you do not have a nested object. If you equate using equal = sign, it will not create a new object, it will just create a variable with the reference to the current object (like a shallow copy).
To do a complete deep copy, even it is nested object, go for this:
const objNoReference = JSON.parse(JSON.stringify(obj));
as suggested by Owl.
Click to read more for better understanding of the concept

A nicer way rather than using JSON.parse, JSON.stringify is:
this.activeValue = {...this.defaultValue}
but this is not natively supported by some browser (IE), unless used with a transpiler (babel)
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
Update
Considering your originial question is about a way in Vue, there is also a native method in vue:
this.activeValue = Vue.util.extend({}, this.defaultValue)
as for this answer.
Hope this helps!

Objects are assigned and copied by reference.
All operations via copied references (like adding/removing properties) are performed on the same single object.
To make a “real copy” (a clone) we can use Object.assign for the so-called “shallow copy” (nested objects are copied by reference).
For “deep cloning” use _.cloneDeep(obj) from loadash library.

JSON stringify&parse method have some issues like converting date objects to strings. It also cannot handle special data types like Map,Set,function etc... This is prone to future bugs.
I use the following method to deep copy an object.
REMEMBER! this is not a complete application of cloning. There are more data types to handle like Blob, RegExp etc...
const deepClone = (inObject) => {
let outObject, value, key
if (typeof inObject !== "object" || inObject === null)
return inObject
if (inObject instanceof Map) {
outObject = new Map(inObject);
for ([key, value] of outObject)
outObject.set(key, deepClone(value))
} else if (inObject instanceof Set) {
outObject = new Set();
for (value of inObject)
outObject.add(deepClone(value))
} else if (inObject instanceof Date) {
outObject = new Date(+inObject)
} else {
outObject = Array.isArray(inObject) ? [] : {}
for (key in inObject) {
value = inObject[key]
outObject[key] = deepClone(value)
}
}
return outObject
}

You can use 'JSON.parse and stringify' or using some clone function in the libs like lodash (underscore, ramda...)

Also a simple solution is to store defaultValue: {json object with some structure} with JSON.stringify(defaultValue) in a string variable:
var x = JSON.stringify(this.defaultValue);
If you need it as JSON object again you can get it with JSON.parse():
var newObject = JSON.parse(x);
The object reference is also broken doing it this way, x will stay unchanged if the content of the object defaultValue is altered.

Related

Keeping nested prototype methods from object transfering from WebWorker

I need to reserialize an object from a WebWorker sharing the same definitions.
Upon reception of the message, I'm losing all the prototype functions.
Doing
worker.onmessage = ({ data }) => {
this.programParser = Object.assign(new ProgramModel(), data);
}
Works only for first level prototype functions, but I need a solution for all the nested classes (this object has a large inheritance and dependency tree).
What could I do to do that ?
Current data model looks like :
(note the Object prototypes)
Using flatted and custom revive function (I added the class name in each object from the serializing function), I can achieve a much closer model to what I would need, but some nested references aren't treated as original class objects.
The flatted revive function is the following :
const model = parse(data, (key, val) => {
if (val !== null && val.className && val.className in models) {
if (val.className === "DataProvider") {
console.log(val)
return new DataProvider(val.providerData)
}
return Object.assign(new (<any>models)[val.className](), val)
}
return val;
})
The flatted library is used to keep clear of circular issues of JSON serialization : https://github.com/WebReflection/flatted
Minimal exemple of the issue : https://codesandbox.io/s/zealous-https-n314w?file=/src/index.ts (Object is lost after 2nd level referencing)
I don't want to take any credit from this previous answer, but while the explanation/process through is nice and sound, I believe the proposed solution is not the best one, and btw, I am the author of the flatted library that actually explained, and helped out, in this issue filed against such library, without even knowing there was a discussion here ... sorry I am late ...
The missing piece of the previous answer is that the same instance gets updated over and over while reviving the whole structure, but nothing like that is actually needed, because either Set or WeakSet can help speeding up the process, avoiding upgrading what's been upgraded already, over and over.
const {setPrototypeOf} = Reflect;
const upgraded = new Set;
const ret = parse(str, (_, v) => {
if (v && v.className && models[v.className] && !upgraded.has(v)) {
upgraded.add(v);
setPrototypeOf(v, models[v.className].prototype);
}
return v;
});
This change doesn't strictly improve the reason it either works or it is the best solution, compared to substitution, but it takes into account performance and redundant upgrades, 'cause unnecessary setPrototypeOf calls, might not be desired at all 😉
This is a classical problem with tying the knot on a circular structure. When deserialising, the flatted library has to start somewhere, passing in the original not-yet-revived object as the argument. It could in theory pass a proxy (or an object with getters) that parse the involved objects on-demand in the right order, but if all objects in the circle need to be revived this would lead to a stack overflow, as JS doesn't use lazy evaluation where you can reference the result of a call before having evaluated it.
A pure approach would actually need a reviver function that does support such a lazy approach, not accessing the object passed to it until after the deserialisation has finished:
const cache = new WeakMap();
const ret = parse(str, (k, v) => {
if (cache.has(v)) return cache.get(v);
if (v && v.className && (<any>models)[v.className]) {
const instance = new (<any>models)[v.className]();
cache.set(v, instance);
for (const p in instance) {
Object.defineProperty(instance, p, {
set(x) { v[p] = x; }, // not necessary if your models were immutable
get() { return v[p]; },
enumerable: true,
});
}
return instance;
}
return v;
});
This essentially makes the instance a proxy over v, getting all its values from there. When flatted does tie the knot by assigning revived values to properties of v, they'll also become available on instance. Apart from the .className, no properties of v are accessed during the reviver call. Only when you access ret.something, the object properties will be read, and will contain the revived objects by then.
The downside of this approach is that a) all models will need to declare and initialise their properties upfront (which your B for example doesn't do) so that the for (const p in instance) loop works, and b) all your model properties will be replaced with accessors, which is inefficient and may conflict with the internal implementation.
An alternative is to forward the property assignments that flatted does on the original object to the newly constructed instance, even if they happen after the reviver call:
const cache = new WeakMap();
const ret = parse(str, (k, v) => {
if (cache.has(v)) return cache.get(v);
if (v && v.className && (<any>models)[v.className]) {
const instance = new (<any>models)[v.className]();
cache.set(v, instance);
Object.assign(instance, v);
for (const p in v) {
Object.defineProperty(v, p, {
set(x) { instance[p] = x; },
get() { return instance[p]; }, // not truly necessary but helps when `v` is logged
});
}
return instance;
}
return v;
});
This basically reflects how you originally constructed the circular reference using a.setB(new B(a)), except by directly assigning the property instead of using the model's setB method, which the reviver doesn't know about. When using this approach, make sure to explicitly document that all your models must support constructor calls without arguments, and direct property assignment (be it via Object.assign or instance[p] = x). If setters are necessary, use accessor properties (set syntax) instead of set…() methods.
The simplest and probably fastest approach would be to not return a new object from the reviver at all, but keep its identity:
const ret = parse(str, (k, v) => {
if (v && v.className) {
const model = (<any>models)[v.className];
if (model && Object.getPrototypeOf(v) != model.prototype) {
Object.setPrototypeOf(v, model.prototype);
}
}
return v;
});
This restores the prototype methods by simply swapping out the prototype of the revived object for the expected one. However, this means that your models are instantiated without a constructor call, they may not use enumerable getters, and they cannot maintain private state even if they expose it through .toJSON correctly.

Props cloned to data variable updating when data variable changes [duplicate]

This question already has answers here:
How do I correctly clone a JavaScript object?
(81 answers)
Closed 2 years ago.
Problem: I have a prop in a Vue component.
props: {
user_info: {
type: Object,
default() {
return {}
}
}
}
I need to use this prop not only for read, but also to make some changes in it. But i cant change the prop, so i cloned it to a data variable
mounted() {
this.clonedUserInfo = {...this.user_info}
}
clonedUserInfo is an empty object in Data option.
But when i change clonedUserInfo, my prop user_info also changes.
So is there any way to brake this Data dependency from Props?
By the way if i am cloning not a Props but a Vuex state variables - it clones fine without changing the base State variable.
Yes there is a way.
mounted() {
this.clonedUserInfo = JSON.parse(JSON.stringify(this.user_info))
}
For now you are copying by reference. And thanks to my answer you will be copying by value.
Look at this SO answer to understand the difference : https://stackoverflow.com/a/430958/3956205
You are shallow copying user_info.
If you only have value only vars in user_info this will work, otherwise you'll update the original values.
You need to deep copy user_info.
LODASH
You can solve this using Lodash's CloneDeep.
npm i --save lodash.clonedeep
const cloneDeep = require('lodash.clonedeep');
NPM: https://www.npmjs.com/package/lodash.clonedeep
Docs: https://lodash.com/docs/4.17.15#cloneDeep
USE A FUNCTION
You can try this to deep clone objects:
function cloneDeepWithoutLodash(src) {
let target = {};
for (const prop in src) {
if (src.hasOwnProperty(prop)) {
if(src[prop] != null && typeof src[prop] === 'object') {
target[prop] = cloneDeepWithoutLodash(src[prop]);
} else {
target[prop] = src[prop];
}
}
}
return target;
}
const source = {a: { b:2 }, c:3};
const target = cloneDeepWithoutLodash(source);
source.a.b = 100; // Lets change the source to check if target changes
console.log(source.a.b); // 100
console.log(target.a.b); // 2
IMPORTANT
Always try to avoid JSON.parse(JSON.stringify(yourObject)).
By doing this you will lose any Javascript property that has no equivalent type in JSON, like Function or Infinity. Any property that’s assigned to undefined will be ignored by JSON.stringify, causing them to be missed on the cloned object.
Also, some objects are converted to strings, like Date objects for example (also, not taking into account the timezone and defaulting to UTC), Set, Map and many others.
You are creating a new object clonedUserInfo that is correct. But all objects inside of clonedUserInfo will be copied by reference.
Try smth like cloneDeep perhaps
https://lodash.com/docs/4.17.15#cloneDeep

Delete an object from a nested array of objects

I have this small function (within my Angular 7 application) which uses JavaScript reduce(), and locates an object within a nested array of objects. I can then proceed to update certain properties on the fly.
Now, in addition to this find logic, I would like to also insert/delete an object into/from the nested array.
Question is: once I do locate my object, can I push() and/or delete an object ?
const input={UID:2,GUID:"",LocationName:"USA",ParentLocation:null,subs:[{UID:42,GUID:"",LocationName:"New Jersey",Description:"",subs:[{UID:3,GUID:"",LocationName:"Essex County",ParentLocation:null,"subs":[{UID:4,LocationName:"Newark",ParentLocation:3,"subs":[{"UID":49,"GUID":"","LocationName":"Doctor Smith's Office","LocationType":{"UID":2,"LocationTypeName":"Practice","Description":"other location"},"subs":[{"HostID":38,"HostName":"Ocean Host",}]}]}]}]}]};
const findUIDObj = (uid, parent) => {
const { UID, subs } = parent;
if (UID === uid) {
const { subs, ...rest } = parent;
return rest;
}
if (subs) return subs.reduce((found, child) => found || findUIDObj(uid, child), null);
};
console.log(findUIDObj(49, input));
var obj = findUIDObj(49, input);
delete obj;
For example, in my Angular 7 app, it complains if I attempt to delete the found object:
ex/
var obj = findUIDObj(49, input);
delete obj;
'delete' cannot be called on an identifier in strict mode.
Looking briefly at your code, I see you are using a const identifier to declare your data collection. We only use const for static data that does not change, and that’s the purpose of it. So, first of all matters, that seems to be the problem. To test it change it to let. Now, as for methods for data management, immutability is worthy of your consideration for many reasons, but namely Angular will rerender the entire object regardless of altering the existing object, or receiveing a new object. You can look up Immutable JavaScript to understand more. In many cases, creating immutable data management is done with a library, you can do it yourself. Basically, create a function called copy( data ), or something so that you pass in the original object, but you get a copy of it in return with no reference to the original object. That way one does not accidentally change the original object. To do this you can do this inside of your copy function: return JSON.parse(JSON.stringify( data )) ;
The only problem you might run into here is deep nested objects, or objects with circular references can cause problems. I have an overriding stringify method to mange this in little libraries I’ve written.
delete obj would never do what you want: first of all, it is not even an object from your input, since the function created a new object from the found object, excluding the subs property, and returned that. But more importantly, delete is used for deleting properties, not objects.
It seems you want to remove a matching object from its parent subs property. For that you would need to mutate the subs array, so it would exclude the matching object. For that to work in a generic way, your input should be an array. Otherwise that root object could not be removed from anything.
With that in mind, your lookup function should return the array in which the match was found and at which index. With those pieces of information you can decide to remove that element from the array, or to insert another object at that index.
Here is how it could work with removal:
const input=[{UID:2,GUID:"",LocationName:"USA",ParentLocation:null,subs:[{UID:42,GUID:"",LocationName:"New Jersey",Description:"",subs:[{UID:3,GUID:"",LocationName:"Essex County",ParentLocation:null,"subs":[{UID:4,LocationName:"Newark",ParentLocation:3,"subs":[{"UID":49,"GUID":"","LocationName":"Doctor Smith's Office","LocationType":{"UID":2,"LocationTypeName":"Practice","Description":"other location"},"subs":[{"HostID":38,"HostName":"Ocean Host",}]}]}]}]}]}];
const findUIDObj = (uid, arr) => {
if (!arr) return;
const idx = arr.findIndex(obj => obj.UID === uid);
if (idx > -1) return [arr, idx];
for (const obj of arr) {
const result = findUIDObj(uid, obj.subs);
if (result) return result;
}
};
console.log(findUIDObj(49, input));
const [arr, idx] = findUIDObj(49, input) || [];
if (arr) {
arr.splice(idx, 1); // Remove object from its parent array
}

javascript object within object issue

I am trying to get the first object within an object.
I have something like
var object = {
task:{
config:'auto',
title :'test'
},
prop:'switch',
time:'5min'
}
My problem is task object could be named differently. so it could be
var object = {
task2:{
config:'manual',
title :'test2'
},
prop:'switch',
time:'5min'
}
I can't use object.task1.config because task name could changed. The task object will always be the first object though.
I want to get the task object no matter what name it has. How do I accomplish that? Thanks!
To get the first property value of an object in modern browsers you could use the Object.keys method:
var myObject = {
whoTheHellKnows: 'foo',
notUsed: 'whatever'
};
var firstProperty = myObject[Object.keys(myObject)[0]];
Working example
Edit: if you do not trust the ordering of the properties (which you should not) AND the task object is the only nested object in your objects, you can rely on type interrogation to find it:
var myObject = {
task123215452: { dahKey: 'foo' },
notUsed: 'whatever',
somethingElse: 42
};
var taskObject;
for (var key in myObject) {
if (typeof myObject[key] === 'object' && !(myObject[key] instanceof Array)) {
taskObject = myObject[key];
break;
}
}
Working example
If you need to access the first key and you don't know the key name, you should really be using an array instead of an object. Objects don't have the concept of a "first" key.
If that's not an option, you're left with for..in or Object.keys, which may fail on some JavaScript implementations because the language specification does not enforce the order of object key enumerations. However, in practice, it will work on current browsers, as they all iterate the keys in the order they were declaredcitation needed thanks to jbabey.

Convert object to JSON omitting certain (private) properties

I've been using dean edwards base.js (http://dean.edwards.name/weblog/2006/03/base/) to organise my program into objects ( base.js is amazing btw, if you havent used it before !).Anyway, my question is generic and you don't have to know base.js to know my answer.
I have a property in one of my objects called ref which is a reference to a DOM element, and this object is meant to be saved as JSON using JSON.stringify, but as you can imagine since DOM elements are circular structure, I won't be able to convert the object into JSON.
Now to get around this problem I have a method called html() which is meant to return the ref property, but I need to have ref as a private property which is only accessible from within the object, and hence won't be sent to stringify.
What's the best way to do that?
You probably know that you cannot have private properties in JavaScript.
Interestingly, if you pass an object to JSON.stringify which has a method toJSON, JSON.stringify will automatically call that method to get a JSONable representation of that object. So all you have to do is implement this method.
For example you can create a shallow copy of the object which only contains the properties you want to copy:
MyConstructor.prototype.toJSON = function() {
var copy = {},
exclude = {ref: 1};
for (var prop in this) {
if (!exclude[prop]) {
copy[prop] = this[prop];
}
}
return copy;
};
DEMO
Another way would be to use a custom replacer function, but it might be more difficult to control which ref to exclude and which one to keep (if different objects have ref properties):
JSON.stringify(someInstance, function(key, value) {
if(key !== 'ref') {
return value;
}
});
DEMO
here is sample to to set variable visibility
function Obj(){
this.ref = 'public property'; // this property is public from within the object
var ref = 'private proerty'; // this property is private.
var self = this;
this.showRef = function(){
alert(ref);
alert(self.ref);
};
}
var obj = new Obj();
obj.showRef();

Categories

Resources