Search code examples
javascriptjsonserializationstringify

Serializing a JavaScript Object to JSON returns "{}", but why?


Hello StackOverflowers,

I'm implementing a LocalStorage system for my web application.

But my pity is that all the objects in the Model abstraction layer have to be serialized.

I am aware that functions don't get serialized when the object is converted to JSON.

I was about to use Option 1 described here. (Which is, creating a 'static' method which returns a brand new object including the functions).

However, the problem is that even the non-function members of the objects don't get converted as well.

For example at divvie.textContent = JSON.stringify( coffee );, divvie.textContent becomes "{}".

Why? What's going on?

Sample code, not my real application but the situation is much the same: Check Coffee

                var coffee = new Coffee( "0001", "DE Red Coarse",
                    "Douwe Egberts, red & coarse grounded." );
                coffee.addSugarCube( "0x0F_BROWN", "15" );
                coffee.addSugarCube( "0x0C_WHITE", "12" );

                var divvie = document.getElementById( "run" );
                divvie.textContent = JSON.stringify( coffee );
        </script>
    </body>
</html>

/**
 * @class This class represents a coffee.
 *
 * @param {String} id
 * @param {String} name
 * @param {String} description
 * @returns {Coffee}
 */
function Coffee( id, name, description )
{
    var sugarCubes = new Array();

    var maxAmountOfSweetness = 0;

    /**
     * @returns {String}
     */
    this.getId = function()
    {
        return id;
    };

    /**
     * @returns {String}
     */
    this.getName = function()
    {
        return name;
    };

    /**
     * @returns {String}
     */
    this.getDescription = function()
    {
        return description;
    };

    /**
     * @param {String} sugarCubeId
     * @param {Number} amountOfSweetness
     */
    this.addSugarCube = function( sugarCubeId, amountOfSweetness )
    {
        /// First check if this sugarCube is already in our coffee.
        var sugarCubeFound = false;

        for( var i = 0; i < sugarCubes.length; i++ )
        {
            if( sugarCubes[ i ].getId() === sugarCubeId )
            {
                sugarCubeFound = true;

                i = sugarCubes.length;
            }
        }

        if( !sugarCubeFound )
        {
            /// Oh Sweet! A new sugar cube to add in our coffee!
            sugarCubes.push( new SugarCube( sugarCubeId, amountOfSweetness ) );
            maxAmountOfSweetness = Math.max( maxAmountOfSweetness, amountOfSweetness );
        }
    };

    /**
     * @param {String} sugarCubeId
     * @returns {SugarCube}
     */
    this.getSugarCube = function( sugarCubeId )
    {
        for( var i = 0; i < sugarCubes.length; i++ )
        {
            if( sugarCubes[ i ].getId() === sugarCubeId )
            {
                return sugarCubes[ i ];
            }
        }
    };

    /**
     * @returns {Boolean} True when the amount of sugar cubes in this coffee is 1 or more,
     * false when not.
     */
    this.isSweet = function()
    {
        if( 0 < sugarCubes.length )
        {
            return true;
        }
        return false;
    };
}

/**
 * @class This class represents a sugar cube
 *
 * @param {String} id
 * @param {Number} amountOfSweetness
 * @returns {SugarCube}
 */
function SugarCube( id, amountOfSweetness )
{
    /**
     * @returns {String}
     */
    this.getId = function()
    {
        return id;
    };

    /**
     * @returns {Number}
     */
    this.getAmountOfSweetness = function()
    {
        return amountOfSweetness;
    };
}

Solution

  • The only properties on instances created by the Coffee constructor seem to be the methods, which have no JSON representation (because they're functions). Your data is all kept as variables or parameters within the closures created. These are not properties and therefore again not something that JSON is designed for.

    To get it working as you expect, you'll either need to write your own toJSON method, or move your data from vars to properties

    For example, using properties

    function Coffee(id, name, description) {
        this.id = id;
        this.name = name;
        this.description = description;
        this.sugarCubes = [];
        this.maxAmountOfSweetness = 0;
    }
    // and now you could even move the methods into the prototype
    Coffee.prototype = {
        getId: function () {return this.id;}
        // etc
    
    };
    // new Coffee() is now JSON-able
    

    You probably used var to "hide" the data so it was only exposed through your methods, so the other way is to add a new method which converts it to JSON, e.g.

    // in Coffee
    this.toJSON = function (a, b) {
        var o = {
            id: id,
            name: name
            // etc
        };
        return JSON.stringify(o, a, b);
    };