10

For some reason I seem unable to use JSON.stringify on a DOMWindow object. For example:

console.log(window.self); // Outputs a hierarchical DOMWindow object
console.log(JSON.stringify(window.self)); // Outputs nothing - not even an error

alert(window.self); // Alerts "[object DOMWindow]"
alert(JSON.stringify(window.self)); // Again nothing - not even an error

Tested on Safari and Chrome. Does anyone have any ideas how I can achieve this?

Edit:

Moved edit to a new question as it's not really specific to this.

ggutenberg
  • 6,880
  • 8
  • 39
  • 48

6 Answers6

3

This answer is not mine, but I was here for the same thing and found this answer elsewhere. I don't have that page open anymore-- I do have the github page still open...

JSON.stringify deep objects -- thanks, @Bergi. https://github.com/Canop/JSON.prune

// JSON.prune : a function to stringify any object without overflow
// two additional optional parameters :
//   - the maximal depth (default : 6)
//   - the maximal length of arrays (default : 50)
// You can also pass an "options" object.
// examples :

//   var json = JSON.prune(window)
//   var arr = Array.apply(0,Array(1000)); var json = JSON.prune(arr, 4, 20)
//   var json = JSON.prune(window.location, {inheritedProperties:true})
// Web site : http://dystroy.org/JSON.prune/
// JSON.prune on github : https://github.com/Canop/JSON.prune
// This was discussed here : https://stackoverflow.com/q/13861254/263525
// The code is based on Douglas Crockford's code : https://github.com/douglascrockford/JSON-js/blob/master/json2.js
// No effort was done to support old browsers. JSON.prune will fail on IE8.
(function () {
    'use strict';

    var DEFAULT_MAX_DEPTH = 6;
    var DEFAULT_ARRAY_MAX_LENGTH = 50;
    var seen; // Same variable used for all stringifications
    var iterator; // either forEachEnumerableOwnProperty, forEachEnumerableProperty or forEachProperty

    // iterates on enumerable own properties (default behavior)
    var forEachEnumerableOwnProperty = function(obj, callback) {
        for (var k in obj) {
            if (Object.prototype.hasOwnProperty.call(obj, k)) callback(k);
        }
    };
    // iterates on enumerable properties
    var forEachEnumerableProperty = function(obj, callback) {
        for (var k in obj) callback(k);
    };
    // iterates on properties, even non enumerable and inherited ones
    // This is dangerous
    var forEachProperty = function(obj, callback, excluded) {
        if (obj==null) return;
        excluded = excluded || {};
        Object.getOwnPropertyNames(obj).forEach(function(k){
            if (!excluded[k]) {
                callback(k);
                excluded[k] = true;
            }
        });
        forEachProperty(Object.getPrototypeOf(obj), callback, excluded);
    };

    Date.prototype.toPrunedJSON = Date.prototype.toJSON;
    String.prototype.toPrunedJSON = String.prototype.toJSON;

    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        meta = {    // table of character substitutions
            '\b': '\\b',
            '\t': '\\t',
            '\n': '\\n',
            '\f': '\\f',
            '\r': '\\r',
            '"' : '\\"',
            '\\': '\\\\'
        };

    function quote(string) {
        escapable.lastIndex = 0;
        return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
            var c = meta[a];
            return typeof c === 'string'
                ? c
                : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
        }) + '"' : '"' + string + '"';
    }

    function str(key, holder, depthDecr, arrayMaxLength) {
        var i, k, v, length, partial, value = holder[key];
        if (value && typeof value === 'object' && typeof value.toPrunedJSON === 'function') {
            value = value.toPrunedJSON(key);
        }

        switch (typeof value) {
        case 'string':
            return quote(value);
        case 'number':
            return isFinite(value) ? String(value) : 'null';
        case 'boolean':
        case 'null':
            return String(value);
        case 'object':
            if (!value) {
                return 'null';
            }
            if (depthDecr<=0 || seen.indexOf(value)!==-1) {
                return '"-pruned-"';
            }
            seen.push(value);
            partial = [];
            if (Object.prototype.toString.apply(value) === '[object Array]') {
                length = Math.min(value.length, arrayMaxLength);
                for (i = 0; i < length; i += 1) {
                    partial[i] = str(i, value, depthDecr-1, arrayMaxLength) || 'null';
                }
                return  '[' + partial.join(',') + ']';
            }
            iterator(value, function(k) {
                try {
                    v = str(k, value, depthDecr-1, arrayMaxLength);
                    if (v) partial.push(quote(k) + ':' + v);
                } catch (e) { 
                    // this try/catch due to forbidden accessors on some objects
                }               
            });
            return '{' + partial.join(',') + '}';
        }
    }

    JSON.prune = function (value, depthDecr, arrayMaxLength) {
        if (typeof depthDecr == "object") {
            var options = depthDecr;
            depthDecr = options.depthDecr;
            arrayMaxLength = options.arrayMaxLength;
            iterator = options.iterator || forEachEnumerableOwnProperty;
            if (options.allProperties) iterator = forEachProperty;
            else if (options.inheritedProperties) iterator = forEachEnumerableProperty
        } else {
            iterator = forEachEnumerableOwnProperty;
        }
        seen = [];
        depthDecr = depthDecr || DEFAULT_MAX_DEPTH;
        arrayMaxLength = arrayMaxLength || DEFAULT_ARRAY_MAX_LENGTH;
        return str('', {'': value}, depthDecr, arrayMaxLength);
    };

    JSON.prune.log = function() {
        console.log.apply(console,  Array.prototype.slice.call(arguments).map(function(v){return JSON.parse(JSON.prune(v))}));
    }
    JSON.prune.forEachProperty = forEachProperty; // you might want to also assign it to Object.forEachProperty

}());
Community
  • 1
  • 1
BradChesney79
  • 650
  • 7
  • 16
  • This seems to be copied from [this answer](http://stackoverflow.com/a/14145840/1048572) – Bergi May 03 '15 at 13:47
  • 1
    @Bergi: The post left the code comments intact which seem to point to all the relevant sources. It seems the repo was in fact based on the answer. – BoltClock May 03 '15 at 13:54
  • Brad, chances are the answer Bergi linked to is where you got the original code from. – BoltClock May 03 '15 at 13:56
  • @BoltClock: Oh right, I didn't notice the comments included the attribution. Thanks! – Bergi May 03 '15 at 13:58
3

In Chrome 8 dev, I get TypeError: Converting circular structure to JSON (window generally contains a self-referential self, window, and top reference, if you're not in a frame), so directly using JSON.stringify will not work.

It sounds like you're using this for debug output. If you care only about some information, you could copy that information to an object, and then stringify it. Then encapsulate it in a function to grab all the information that you think you'll ever care about from window.

var data = JSON.stringify({
    'location': window.location
    // etc
});
theazureshadow
  • 9,499
  • 5
  • 33
  • 48
3

Why would you want to do serialize the DOM? If you must, Crescent's link is where you need to look. The reason you cannot serialize (stringify) the window object is because it contains circular references, and JSON.stringify does not support them by default.

Alex
  • 64,178
  • 48
  • 151
  • 180
  • @dosboy pay attention to the *"why would you want to do it"*. There's hardly any good reason to serialize the DOM. – cregox Apr 14 '11 at 19:13
  • @Alex A lot of time has gone by, do you guys know of any way it would be possible to send a DOM object through an IPC module? – Kragalon Mar 12 '16 at 02:51
  • 1
    It doesn't matter why the OP wants to serialize the DOM. They just want to know how it can be done. This "answer" doesn't provide any solution (which there are) to the OP's problem. – Evan Wieland Feb 26 '20 at 19:34
3

As others have said, stringify doesn't support circular references, which DOMWindow contains. Generally, circular references could be converted to JSON using Douglas Cockford's JSON cycle.js.

However, I just tried this on window, and it causes a stack overflow anyway. While that may be a bug in the cycle.js code, it seems like its more likely that window is just too big of an object.

Andrew Shooner
  • 1,170
  • 3
  • 11
  • 16
2

You don't get an error? I get TypeError: Converting circular structure to JSON. I would say, it cannot be done.

Also, window and window.self point to the same object (the Global object), so you don't need to use that property...

Šime Vidas
  • 182,163
  • 62
  • 281
  • 385
1

You can use this function to stringify the window object:

function recur(obj, visited = new WeakSet()) {
    if (visited.has(obj)) {
        return {}; // skip already visited object to prevent cycles
    }
    visited.add(obj); // add the current object to the visited set

    var result = {}, _tmp;
    for (var i in obj) {
        try {
            // enabledPlugin is too nested, also skip functions
            if (i === 'enabledPlugin' || typeof obj[i] === 'function') {
                continue;
            } else if (typeof obj[i] === 'object') {
                // get props recursively
                _tmp = recur(obj[i], visited);
                // if object is not {}
                if (Object.keys(_tmp).length) {
                    result[i] = _tmp;
                }
            } else {
                // string, number or boolean
                result[i] = obj[i];
            }
        } catch (error) {
            // handle error, you can log it here if needed
            // console.error('Error:', error);
        }
    }
    return result;
}
JSON.stringify(recur(window))
Matt
  • 195
  • 3
  • 8
  • Nice, this worked! I recommend you run this in the Console on a `about:blank` page because it will stringify the page contents. This crashed my browser. Or run this in a blank HTML page. – Lewis Nakao Jul 14 '23 at 21:48