7

Scenario

After reading this answer I realized that I could create object starting from a JSON literal.

So I guessed that I could do the opposite just using this useful JSON method: JSON.stringify(myObject).

So I did as follow:

function MyObject(id, value, desc)
{
  this.id = id;
  this.value = value;
  this.desc = desc;
  this.toJSON = function()
  {
    return JSON.stringify(this);
  }

}

But when I run this stuff (demo) a Maximum call stack size exceeded error occurs.

After googling a bit, I found two references that explain this behaviour:

If I get right, .toJSON overrides the .stringify. So if the first one calls the second one a loop is generated.

Questions

  1. (general) Why this design choice? toJSON is a kind of reserved of special keyword?
  2. (specific) I solved the stackoverflow bug changing the .toJSON name into .display. Not so elegant. Is there another solution?
3
  • 2
    +1 for using the stackoverflow keyword. No, kidding, it's a good question. Commented Oct 9, 2012 at 8:52
  • @ADC Remember to accept your open questions. Commented Oct 12, 2012 at 11:45
  • Why do you use a custom toJSON method if there is nothing special? Commented Oct 19, 2012 at 13:22

2 Answers 2

6

Think it's because toJSON is semi reserved: stringify will check the object and see if it's has a method called toJSON and then try to call it to string the result.


A workaround can be: (Not sure about the reliablity of this code)

var obj = {
    value: 1,
    name: "John",
    toJSON: function() {
        var ret,
            fn = this.toJSON;

        delete this.toJSON;

        ret = JSON.stringify(this);

        this.toJSON = fn;

        return ret;
    }
}

Usage:

obj.toJSON(); // "{\"value\":1,\"name\":\"John\"}"
obj.lastName = "Smith";
obj.toJSON(); // "{\"value\":1,\"name\":\"John\",\"lastName\":\"Smith\"}"

Maybe using a clousure is a little prettier: (And then I think I can say it's safe)

var obj = {
    value: 1,
    name: "John",
    toJSON: (function() {
        function fn() {
            var ret;
            delete this.toJSON;

            ret = JSON.stringify(this);

            this.toJSON = fn;

            return ret;
        }
        return fn;
    })()
}

So after reading @filmor's comment i thoght about another way to handle this. Not that pretty but it works.

Using Function.caller I can detect if fn is called using JSON.stringify

var obj = {
    value: 1,
    name: "John",
    toJSON: (function() {
        return function fn() {
            var ret;

            delete this.toJSON;

            ret = JSON.stringify(this);

            if ( fn.caller === JSON.stringify ) {
                ret = JSON.parse( ret );
            }

            this.toJSON = fn;

            return ret;
        }
    })()
}
Sign up to request clarification or add additional context in comments.

3 Comments

Found that out at roughly the same time as you (after crashing Firefox during the course ;)). One comment: If you do it like this you break normal JSON.stringify(obj), since it will interpret the object as a string and escape and add surrounding " chars.
@filmor I ran into the same problem. But think I found a solution using fn.caller
Interesting, but quite a lot of stuff just to use toJSON intead of display :). Note that there is a non standard remark in the Function.caller page. Anyway, an elegant solution.
3
+50

Question 1, is toJSON reserved?

I'm not sure if it reserved, but for example the native Date object uses toJSON to create a stringified date representation:

(new Date()).toJSON();           // -> "2012-10-20T01:58:21.427Z"
JSON.stringify({d: new Date()}); // -> {"d":"2012-10-20T01:58:21.427Z"}"

Question 2, an easy solution:

create your custom stringify function that ignores toJSON methods (you may add it to the already existing global JSON):

JSON.customStringify = function (obj) {

    var fn = obj.toJSON;
    obj.toJSON = undefined;
    var json = JSON.stringify(obj);
    obj.toJSON = fn;
    return json;
}

now it's very easy to use in all your objects:

function MyObject(id, value, desc)
{
  this.id = id;
  this.value = value;
  this.desc = desc;
  this.toJSON = function()
  {
    return JSON.customStringify(this);
  }
}

To make it even more easy additionally add:

JSON.customStringifyMethod = function () {

    return JSON.customStringify(this);
}

Now your objects might look like:

function MyObject(id, value, desc)
{
  this.id = id;
  this.value = value;
  this.desc = desc;
  this.toJSON = JSON.customStringifyMethod;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.