1

My desire is a "safe" way to encode a given object to JSON. This is for a logging any abstract object in Node.js but can apply to other needs as well. My research pointed me to util.inspect in Node, but this isn't valid JSON, and my desire is to have valid JSON that I can re-hydrate later. Because some Error instances can have recursion issues, I needed to limit the response. Also, in some cases forward and back-references to objects can be an issue as well, so I want to avoid duplicate entries.

4

2 に答える 2

0

必要なものであるstringifyメソッドを備えたネイティブのjavascriptJSONオブジェクトがあります:http ://www.javascripture.com/JSON#stringify

于 2012-11-29T14:24:18.490 に答える
0

NEW ANSWER

Today, in case anyone is looking at this, I would just use fclone which at this point is much more thorough than my original.


Original Answer:

I'm answering my own question below, as this was a bit difficult to come up with a working solution. It's the combination of a few solutions I've found, and seems to work well.

/* lib/clone-safe.js
*  provides a method for creating a clone of an object without redundant object references, and protected from recursion.
*  The cloned value *should* always be safe for JSON.stringify(cloneSafe(myobj))
**/
(function(){
    var undef; //intentionally undefined value
    function cloneSafe(obj,depth){
        var refs = []; //reference to cloned objects
        depth = +depth > 0 && +depth || 6; //max recursion level

        var layerNumber = 0; //current layer being checked
        var ret = clone(obj); //start cloning

        //cleanup reference checks
        while(refs.length) {
            delete (refs.shift()).___copied;
        }

        //return the result
        return ret;

        //recursive clone method
        function clone(obj) {
            if (typeof obj == "function") return undef; //no function replication

            // Handle the 3 simple types, and null or undefined
            if (null == obj || "object" != typeof obj) return obj;

            // Handle Date
            if (obj instanceof Date) {
                var copy = new Date();
                copy.setTime(obj.getTime());
                return copy;
            }

            // Handle Array
            if (obj instanceof Array) {
                var copy = [];
                for (var i = 0, len = obj.length; i < len; i++) {
                    copy[i] = clone(obj[i]);
                }
                return copy;
            }

            // Handle Object
            if (obj instanceof Object) {
                //max recursion reached
                if (++layerNumber >= depth) {
                    layerNumber--;
                    return undef;
                }

                //handle circular and duplicate references
                if (obj.___copied) return undef; //already included
                obj.___copied = true;
                refs.push(obj);

                var copy = {};

                //export prototype
                var m = obj.constructor && obj.constructor.toString().match(/function\s+([^\(]+)/);
                if (m && m[1]) copy._prototype = m[1];

                //get expected properties from any error
                if (obj instanceof Error) {
                    copy.message = obj.message || "Error";
                    if (obj.stack) copy.stack = obj.stack;
                    if (obj.number) copy.number = obj.number;
                    if (obj.description) copy.description = obj.description;
                    if (obj.name) copy.name = obj.name;
                }

                for (var attr in obj) {
                    if (attr == "___copied") continue;
                    if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
                }
                if (obj.prototype) {
                    for (var attr in obj.prototype) {
                        if (obj.prototype.hasOwnProperty(attr) && typeof obj.prototype[attr] !== 'function') copy[attr] = clone(obj.prototype[attr]);
                        delete obj.prototype[attr].___copied; //allow prototypes to be re-scanned
                    }
                }
                layerNumber--;
                return copy;
            }

            //throw new Error("Unable to copy obj! Its type isn't supported.");
            console.log("Unable to copy obj! Unsupported type: %s", typeof obj);
            return undef; //unable to clone the object in question
        }
    }

    // AMD / RequireJS
    if (typeof define === "function" && define.amd) {
        define("clone-safe",[],function(){ return cloneSafe; });
    }
    // Node.js / CommonJS
    else if (typeof module !== "undefined" && module.exports) {
        module.exports = cloneSafe;
    }
    // browser/script include
    else {

        //provide a method for reverting global binding
        var root = this; //global on the server, window in the browser.
        var previousCloneSafe = root.cloneSafe; //backreference
        cloneSafe.noConflict = function(){
            root.cloneSafe = previousCloneSafe;
            return cloneSafe;
        };

        //bind to the global object
        root.cloneSafe = cloneSafe;

    }

}());

There is some extra logic for dealing with objects that are errors, and making sure to get necessary data from said errors, and from a given object's prototype. functions are expressly ignored in the response. When recursion goes too deep, it will return undefined for a given object.

于 2012-11-27T22:49:51.990 に答える