5

Is there a way to get just a shallow copy of the below to only get one layer deep? I have a means to fix this using a completely different design but I was wondering if anyone else had ran into what I am trying to convert to a string before.

var SomeObjClass = function() {
    var self = this;
    this.group = {
        members: [self]
    };
};
var p = new SomeObjClass();
var str = JSON.stringify(p);
simon
  • 854
  • 1
  • 9
  • 23
  • error ? Converting circular structure to JSON – Mahi Nov 05 '16 at 17:14
  • Can you provide an example of your expected result? – Vlad Holubiev Nov 05 '16 at 17:16
  • 1
    There's nothing to copy. Doing a shallow clone would be the same as just re-invoking your constructor to make a new object. I assume there's other code that you haven't pasted. – maček Nov 05 '16 at 17:17
  • Expected result would be: console.log(str); Object { group: { members: { Object: { group: { members: {} } } } } } This is for the 'shallow clone' I'm asking is possible. The problem is that toJSON will recursively go until stack is overflowed. – simon Nov 05 '16 at 17:54
  • Related: [Limit JSON stringification depth](https://stackoverflow.com/questions/16466220/limit-json-stringification-depth) – krubo Jan 27 '21 at 15:31

4 Answers4

2

It's a little unclear what you're asking, but if your goal is to simply stringify a circular object, you'll have to override toJSON to specify how you'd like your object to be represented

function SomeObjClass () {
  var self = this;
  this.group = {
    members: [self]
  };
}

SomeObjClass.prototype.addMember = function(m) {
  this.group.members.push(m);
};

// when stringifying this object, don't include `self` in group.members
SomeObjClass.prototype.toJSON = function() {
  var self = this;
  return {
    group: {
      members: self.group.members.filter(function (x) {
        return x !== self
      })
    }
  };
}

var a = new SomeObjClass();
var b = new SomeObjClass();
a.addMember(b);

console.log(JSON.stringify(a))

This is probably the best I can help you without seeing more of your code. I don't know how you're using this code, but whatever the case, this is probably not the best design for your class. If you share the rest of the class and the code that uses it, we can probably help you more effectively.

Mulan
  • 129,518
  • 31
  • 228
  • 259
  • This is one method around but I was wondering if toJSON had specific parameters I could modify in the call to do what I requested as 'shallow clone', but in this case you are overriding what it natively does. I will upvote this and probably ignore this design pattern and look at alternative ways around it. The entire reason for this post is because while circular references are fine in a client, when trying to do things like sending the data from one client to another over a network, stack overflow was ensuing due to the circular reference. – simon Nov 05 '16 at 18:00
1

If you check MDN reference about JSON.stringify, you'll see that it accepts as a second parameter a replacer function. This function is useful to massage a bit the elements that you want stringified.

It could help you to avoid your circular problem.

For instance:

function avoidCircularReference(obj) {
  return function(key, value) {
    return key && typeof value === 'object' && obj === value ? undefined : value;
  };
}

var SomeObjClass = function() {
    var self = this;
    this.group = {
        members: [self, {a:'f', b: [self]}]
    };
};
var p = new SomeObjClass();
var str = JSON.stringify(p, avoidCircularReference(p));
console.log(str);

However, and as stated in the documentation and shown running the example:

Note: You cannot use the replacer function to remove values from an array. If you return undefined or a function then null is used instead.

So you'd have to deal in some way with these nulls. Anyway, you could play with this function and "adapt" it to your needs. For instance and applied to your example:

function avoidCircularReference(obj) {
  var removeMeFromArray = function(arr) {
    var index = arr.indexOf(obj);
    if (index > -1) {
      arr.splice(index, 1);
    }
  };

  return function(key, value) {
    if (Object.prototype.toString.call(value) === "[object Array]") {
      removeMeFromArray(value);
    }
    return value;
  };
}

var SomeObjClass = function() {
  var self = this;
  this.group = {
    members: [self, {
      a: 'f',
      b: [self]
    }]
  };
};
var p = new SomeObjClass();
var str = JSON.stringify(p, avoidCircularReference(p));
console.log(str);
acontell
  • 6,792
  • 1
  • 19
  • 32
0

To solve the problem and keep the JSON.stringify simplicity, I use the following approach (here in my dehydrate method)

public dehydrate(): string {
    var seenObjects = [];
    function inspectElement(key, value) {
        if (detectCycle(value)) {
            return '[Ciclical]';
        } else {
            return value;
        };
    };
    function detectCycle(obj): boolean {
        if (obj && (typeof obj == 'object')) {
            for (let r of seenObjects) {
                if (r == obj) {
                    return true;
                };
            };
            seenObjects.push(obj);
        };
        return false;
    };
    let json: string = JSON.stringify(this, inspectElement,'  ');
    return json;
};

Note that although this is TypeScript, using strong types to achieve the results inside the method would lead us to some confusion.

Unfortunately I had to use for instead of array search because it simply didn't work to me.

Nathan Tuggy
  • 2,237
  • 27
  • 30
  • 38
JLCDev
  • 609
  • 1
  • 5
  • 18
0

This is an implementation that worked for me. It relies on reference comparison for equality, which should be fine for this purpose.

I include the index of the offending object in the replacer return value so that in principle, you could restore the cyclic object when deserializing.

function safeJsonStringify(value) {
  const visitedObjs = [];
  function replacerFn(key, obj) {
    const refIndex = visitedObjs.indexOf(obj);
    if (refIndex >= 0) return `cyclic-ref:${refIndex}`;
    if (typeof obj === 'object' && obj !== null) visitedObjs.push(obj);
    return obj;
  }
  return JSON.stringify(value, replacerFn);
}
// Take it for a spin:
const cyclic = { greeting: 'Hello!' };
cyclic.badRef = cyclic;
console.log(safeJsonStringify(cyclic));
LOAS
  • 7,161
  • 2
  • 28
  • 25