How to copy a JSON object without reference in Vue.js?
Asked Answered
J

8

14

In my component I have declared some data:

data() {
    return {
        defaultValue: {json object with some structure},
        activeValue: {}
        ...

And within the component I assign defaultValue to activeValue:

this.activeValue = this.defaultValue

But the problem is, after I change this.activeValue value the changes affect this.defaultValue too.

If I use Object.freeze(this.defaultValue) and then I try to change this.activeValue I get an error - object is not writable.

How can I make a copy of the data but without reference ?

Jigsaw answered 11/7, 2020 at 10:13 Comment(0)
H
20

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));
Hasidism answered 11/7, 2020 at 10:15 Comment(2)
Beware, this method converts dates to string, and won't copy special types like MapPygmy
@Hasidism Does it the standard and best practice if we deal with a bunch of data in the object?Rainproof
T
9
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

Tachistoscope answered 11/7, 2020 at 10:26 Comment(3)
There's a confusion with terms. { ...this.defaultValue } isn't destructuring but spread syntax. It creates a shallow and not deep copy - unless you do this explicitly with { ...this.defaultValue, foo: { ...this.defaultValue.foo } }. = copies by reference and doesn't make a shallow copy.Went
JSON method removes data types like Map & Regex and also stringfy Date Very possible to cause bugsPygmy
@Pygmy true. Thanks for pointing it out. If the value of any of the keys in the object (in nested object also) is Map, Date, Function, Regex, Set etc it won't work exact clone of the object.Tachistoscope
M
6

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!

Malloy answered 11/7, 2020 at 10:19 Comment(1)
This creates a shallow copy and won't work as intended for nested objects.Went
C
1

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.

Cargile answered 11/7, 2020 at 10:58 Comment(0)
P
1

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
        }
Pygmy answered 15/10, 2021 at 12:21 Comment(0)
S
1

In case you are using Vue.js 3 and want to create a reactive variable containing the data of another variable, I would suggest using ref() rather than reactive() because a reactive() variable cannot be re-assigned.

The way I do this is by assigning the reactive variable within the onMounted() lifecycle hook. So that I can first declare the ref() variable and then assign the data to it.

With composition API, it would look something like so:

const data = [{a: 1, b: 2 }, {a: 2, b: 1}]

const reactiveData = ref();

onMounted(() => reactiveData.value = JSON.parse(JSON.stringify(data)) )

Why use JSON.parse and JSON.stringify ?

By doing this two step process of stringifying and then parsing the JSON representation of the object, you can create a deep clone of the object without maintaining any references to the original object.


Now that data is not a target for the reactive variable you can check if there are differences between them, like so:

const changesMade = computed(() => {
    return !(JSON.stringify(reactiveData.value) === JSON.stringify(data));
  });

Sorcim answered 8/3 at 22:0 Comment(0)
D
0

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

Debt answered 11/7, 2020 at 10:32 Comment(0)
F
-1

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.

Ferryboat answered 27/9, 2021 at 23:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.