Search code examples
javascriptjsonnode.jsstringify

Best practices for stringifying JSON inner objects


Basically I have something like this:

MyClass

var MyClass = function() {
  this.num = 123;
  this.obj = new MyInnerClass();
};

MyClass.prototype.stringify = function() {
  return JSON.stringify(this);
};

MyInnerClass

var MyInnerClass = function() {
  this.foo = 456;
  this.bar = 'bonjour!';
};

MyInnerClass.prototype.stringify = function() {
  return JSON.stringify(this, function(k, v) {
    // ignores 'foo' attribute
    return k !== 'foo' ? v : undefined;
  });
};

Each class has its own stringify implementation, so when I do:

var mc = new MyClass();
mc.stringify();

I would like something like calling MyClass.stringify should stringify my mc object, but respecting inner objects stringify implementations. Once we don't have control over the JSON.stringify method logic, is there a good way to do that?

Thank you!


Solution

  • If you look on MDN at JSON.stringify, you'll see a section that talks about a toJSON property

    If an object being stringified has a property named toJSON whose value is a function, then the toJSON() method customizes JSON stringification behavior: instead of the object being serialized, the value returned by the toJSON() method when called will be serialized.

    Basically, define a toJSON method for your Object which creates another Object, but one that can be serialised as you desire. Then JSON.stringify will serialise the return of your toJSON function instead, i.e.

    var MyClass = function() {
      this.num = 123;
      this.obj = new MyInnerClass();
    };
    
    var MyInnerClass = function() {
      this.foo = 456;
      this.bar = 'bonjour!';
    };
    
    MyInnerClass.prototype.toJSON = function () {
        // shallow clone `this`, except .foo
        var o = Object.create(null), k, blacklist = ['foo'];
        for (k in this) // loop over enumerable properties
            if (Object.prototype.hasOwnProperty.call(this, k)) // ignore inherited properties
                if (blacklist.indexOf(k) === -1) // ignore blacklisted properties
                    o[k] = this[k]; // add to our clone
        return o;
    };
    
    JSON.stringify(new MyClass()); // '{"num":123,"obj":{"bar":"bonjour!"}}'
    

    This will also replace your need for the current stringify method.


    Sadly you can't call JSON.stringify(this) inside .toJSON because it becomes circular and you get RangeError: Maximum call stack size exceeded, but you'd not get the desired result this way anyway as it would be serialised a second time, giving you a String in your JSON.