Monday, August 29, 2016

A safer JSON.stringify + Save to file


Safe Stringify


This code follows links once and only once, so, there should be no circular references.

Note that large objects should NOT be output to the console, as this will be super slow.

Assign the safeStringify to a variable and save it to a file using console.save below.

See the sample usage at the end of this article.

var safeStringify = function(obj, replacer, indent) {
    var printedObjects = [];
    var printedObjectKeys = [];
    function is_scalar(obj){return (/string|number|boolean/).test(typeof obj);}
    function printOnceReplacer(key, value) {
        if (value === null || value === undefined || is_scalar(value))
            return value;
        if (printedObjects.length > 10000) {
            return 'object too long';
        }
        // try to find the current object in "printedObjects"
        var printedObjIndex = false;
        printedObjects.forEach(function(obj, index) {
            if (obj === value) {
                printedObjIndex = index;
            }
        });

        // handle root case
        if (key == '') {
            //root element
            printedObjects.push(obj);
            printedObjectKeys.push("root");
            return value;
        } 
        // hadle case previously found reference case
        else if (printedObjIndex + "" != "false" && typeof (value) == "object") {
            if (printedObjectKeys[printedObjIndex] == "root") {
                return "(pointer to root)";
            } else {
                return "(see " + ((!!value && !!value.constructor) ? value.constructor.name.toLowerCase() : typeof (value)) + " with key " + printedObjectKeys[printedObjIndex] + ")";
            }
        } 
        // hanle newly found reference case, and calling the replacer
        else {
            var qualifiedKey = key || "(empty key)";
            printedObjects.push(value);
            printedObjectKeys.push(qualifiedKey);
            if (replacer) {
                return replacer(key, value);
            } else {
                return value;
            }
        }
    }
    return JSON.stringify(obj, printOnceReplacer, indent);
};

ref: modified from http://stackoverflow.com/a/17773553/279393

console.save


(function(console){

    console.save = function(data, filename){

        if(!data) {
            console.error('Console.save: No data')
            return;
        }

        if(!filename) filename = 'console.json'

        if(typeof data === "object"){
            data = JSON.stringify(data, undefined, 4)
        }

        var blob = new Blob([data], {type: 'text/json'}),
            e    = document.createEvent('MouseEvents'),
            a    = document.createElement('a')

        a.download = filename
        a.href = window.URL.createObjectURL(blob)
        a.dataset.downloadurl =  ['text/json', a.download, a.href].join(':')
        e.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null)
        a.dispatchEvent(e)
    }
})(console)


ref: http://bgrins.github.io/devtools-snippets/#console-save


Simple Usage

var BNes = safeStringify(myVariableToStringify, undefined, 4);

console.save(BNes);

More Complex Usage

var BNes = safeStringify(myVariableToStringify, (k, v) => {
    let replacements = {"_parent": "[CIRCULAR]", "domElem": "[DOM ELEMENT]"};
    // Replace references to HTML elements with a string
    if (v && v.constructor) {
        let cn = v.constructor.name.toLowerCase();
        if (cn.indexOf("element") === cn.length - 7 && cn.indexOf("html") !== -1)
        return "HTML ELEMENT";
    }
    if (replacements[k] !== undefined)
        return replacements[k];
    else
        return v;
}, 4);

console.save(BNes);