Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

javaScript: Parsing a stringified object with JSON.parse removes references

I am trying to deep-clone an object, say "a" with k = JSON.parse(JSON.stringify(a)). It is important that I use the stringify way, since I am trying to save the object into a file and then load from it.

I stumbled upon a problem with references on the cloned object which is illustrated below:

var obj={};
obj.importantProperty={s:2};
obj.c=obj.importantProperty;
obj.d=obj.importantProperty;
console.log( obj.c === obj.d ); // Returns true

var cloned = JSON.parse(JSON.stringify(obj));
console.log( cloned.c === cloned.d ); // Returns false
I need the references to be kept when using JSON.parse, in the above example they are not. In my project the object is much more complicated, but in the end it comes down to the example above.

Thanks in advance to anyone who helps me with this :)

like image 689
Martin Antonov Avatar asked Jun 21 '17 11:06

Martin Antonov


1 Answers

The proper way to do something like this would be to store the common referenced object(s) separately and reference it by an ID.

For instance, you can hold your importantProperty objects in an array and use the index as the ID:

var importantProperties = [
  { s: 1 },
  { s: 2 },
  { s: 3 }
];

var obj = {};
obj.importantProperty = importantProperties[1];
obj.c = obj.importantProperty;
obj.d = obj.importantProperty;

Then when you stringify the object you replace the referenced object with its index:

var stringified = JSON.stringify(obj, function(key, value) {
  if (key) {
    return importantProperties.indexOf(value);
  }
  return value;
});
console.log(stringified);
// prints {"importantProperty":1,"c":1,"d":1}

And then when you parse you simply reverse the process to revive the references:

var parsed = JSON.parse(stringified, function(key, value) {
  if (key) {
    return importantProperties[value];
  }
  return value;
});
console.log(parsed.c === parsed.d && parsed.d === parsed.importantProperty);
// prints true

Now, the example above works for your example code under the assumption that all properties in obj is an object from the importantProperties array. If that's not the case and it's only certain properties that is an importantProperties object, you need to check for that when replacing/reviving.
Assuming only the "importantProperty", "c" and "d" properties are such objects:
if (['importantProperty', 'c', 'd'].includes(key)) instead of just if (key)

If this isn't good enough and you don't want the property name to have anything to do with whether or not the value is an importantProperties object, you'll need to indicate this in the value together with the identifier. Here's an example of how this can be done:

// Replacing
JSON.stringify(obj, function(k, value) {
  if (importantProperties.includes(value)) {
    return 'ImportantProperty['
      + importantProperties.indexOf(value)
      + ']';
  }
  return value;
});

// Reviving
JSON.parse(stringified, function(k, value) {
  if (/^ImportantProperty\[\d+\]$/.test(value)) {
    var index = Number( value.match(/\d+/)[0] );
    return importantProperties[index];
  }
  return value;
});
like image 149
Lennholm Avatar answered Nov 14 '22 22:11

Lennholm