JSON.stringify oggetti profondi

Ho bisogno di una funzione per build una stringa JSON valida da qualsiasi argomento, ma:

  • evitando il problema della ricorsività non aggiungendo oggetti due volte
  • evitando il problema delle dimensioni dello stack di chiamate troncando oltre una determinata profondità

Generalmente dovrebbe essere in grado di elaborare oggetti grandi, a costo di troncarli.

Come riferimento, questo codice ha esito negativo:

var json = JSON.stringify(window); 

Evitare il problema della ricorsività è abbastanza semplice:

 var seen = []; return JSON.stringify(o, function(_, value) { if (typeof value === 'object' && value !== null) { if (seen.indexOf(value) !== -1) return; else seen.push(value); } return value; }); 

Ma per ora, oltre a copiare e modificare il codice di Douglas Crockford per tenere traccia della profondità, non ho trovato alcun modo per evitare il sovraccarico dello stack su oggetti molto profondi come la window o qualsiasi event . C’è una soluzione semplice?

    Ho fatto quello che inizialmente temevo dovessi fare: ho preso il codice di Crockford e l’ho modificato per i miei bisogni. Ora costruisce JSON ma gestisce

    • cicli
    • oggetti troppo profondi
    • matrici troppo lunghe
    • eccezioni (accessor che non possono essere legalmente accessibili)

    Nel caso qualcuno ne abbia bisogno, ho creato un repository GitHub: JSON.prune su GitHub

    Ecco il codice:

     // JSON.pruned : a function to stringify any object without overflow // example : var json = JSON.pruned({a:'e', c:[1,2,{d:{e:42, f:'deep'}}]}) // two additional optional parameters : // - the maximal depth (default : 6) // - the maximal length of arrays (default : 50) // GitHub : https://github.com/Canop/JSON.prune // This is based on Douglas Crockford's code ( https://github.com/douglascrockford/JSON-js/blob/master/json2.js ) (function () { 'use strict'; var DEFAULT_MAX_DEPTH = 6; var DEFAULT_ARRAY_MAX_LENGTH = 50; var seen; // Same variable used for all stringifications 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, // The loop counter. k, // The member key. v, // The member value. 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'; } v = partial.length === 0 ? '[]' : '[' + partial.join(',') + ']'; return v; } for (k in value) { if (Object.prototype.hasOwnProperty.call(value, k)) { try { v = str(k, value, depthDecr-1, arrayMaxLength); if (v) partial.push(quote(k) + ':' + v); } catch (e) { // this try/catch due to some "Accessing selectionEnd on an input element that cannot have a selection." on Chrome } } } v = partial.length === 0 ? '{}' : '{' + partial.join(',') + '}'; return v; } } JSON.pruned = function (value, depthDecr, arrayMaxLength) { seen = []; depthDecr = depthDecr || DEFAULT_MAX_DEPTH; arrayMaxLength = arrayMaxLength || DEFAULT_ARRAY_MAX_LENGTH; return str('', {'': value}, depthDecr, arrayMaxLength); }; }()); 

    Un esempio di cosa si può fare:

     var json = JSON.pruned(window); 

    Nota: Contrariamente al codice in questa risposta, il repository GitHub viene aggiornato quando necessario (documentazione, compatibilità, uso come modulo in commonjs o nodo, serializzazioni specifiche, ecc.). È una buona idea partire dal repository se hai bisogno di questa funzione di eliminazione.

    Se stai usando Node.js puoi usare util.inspect , che accetta un argomento di profondità.

    Puoi semplicemente utilizzare una funzione Censore come nell’esempio seguente:

     function censor(key, value) { if (typeof(value) == "string") { return undefined; } return value; } var foo = {foundation: "Mozilla", model: "box", week: 45, transport: "car", month: 7}; var jsonString = JSON.stringify(foo, censor); 

    L’output è {"week":45,"month":7} .

    Così come per il tuo esempio, devi tornare indefinito se hai un object valore, che è una finestra.

    Ho rivisto la risposta di @ dystroy, aggiungendo:

    • Rientro per sotto-proprietà.
    • Un’indicazione di dove puntano i riferimenti circolari.
     /** * Returns the JSON representation of an object. * * @param {value} object the object * @param {number} objectMaxDepth for objects, the maximum number of times to recurse into descendants * @param {number} arrayMaxLength for arrays, the maximum number of elements to enumerate * @param {string} indent the string to use for indentation * @return {string} the JSON representation */ var toJSON = function(object, objectMaxDepth, arrayMaxLength, indent) { "use strict"; /** * Escapes control characters, quote characters, backslash characters and quotes the string. * * @param {string} string the string to quote * @returns {String} the quoted string */ function quote(string) { escapable.lastIndex = 0; var escaped; if (escapable.test(string)) { escaped = string.replace(escapable, function(a) { var replacement = replacements[a]; if (typeof (replacement) === "string") return replacement; // Pad the unicode representation with leading zeros, up to 4 characters. return "\\u" + ("0000" + a.charCodeAt(0).toString(16)).slice(-4); }); } else escaped = string; return "\"" + escaped + "\""; } /** * Returns the String representation of an object. * * Based on https://github.com/Canop/JSON.prune/blob/master/JSON.prune.js * * @param {string} path the fully-qualified path of value in the JSON object * @param {type} value the value of the property * @param {string} cumulativeIndent the indentation to apply at this level * @param {number} depth the current recursion depth * @return {String} the JSON representation of the object, or "null" for values that aren't valid * in JSON (eg infinite numbers). */ function toString(path, value, cumulativeIndent, depth) { switch (typeof (value)) { case "string": return quote(value); case "number": { // JSON numbers must be finite if (isFinite(value)) return String(value); return "null"; } case "boolean": return String(value); case "object": { if (!value) return "null"; var valueIndex = values.indexOf(value); if (valueIndex !== -1) return "Reference => " + paths[valueIndex]; values.push(value); paths.push(path); if (depth > objectMaxDepth) return "..."; // Make an array to hold the partial results of stringifying this object value. var partial = []; // Is the value an array? var i; if (Object.prototype.toString.apply(value) === "[object Array]") { // The value is an array. Stringify every element var length = Math.min(value.length, arrayMaxLength); // Whether a property has one or multiple values, they should be treated as the same // object depth. As such, we do not increment the object depth when recursing into an // array. for (i = 0; i < length; ++i) { partial[i] = toString(path + "." + i, value[i], cumulativeIndent + indent, depth, arrayMaxLength); } if (i < value.length) { // arrayMaxLength reached partial[i] = "..."; } return "\n" + cumulativeIndent + "[" + partial.join(", ") + "\n" + cumulativeIndent + "]"; } // Otherwise, iterate through all of the keys in the object. for (var subKey in value) { if (Object.prototype.hasOwnProperty.call(value, subKey)) { var subValue; try { subValue = toString(path + "." + subKey, value[subKey], cumulativeIndent + indent, depth + 1); partial.push(quote(subKey) + ": " + subValue); } catch (e) { // this try/catch due to forbidden accessors on some objects if (e.message) subKey = e.message; else subKey = "access denied"; } } } var result = "\n" + cumulativeIndent + "{\n"; for (i = 0; i < partial.length; ++i) result += cumulativeIndent + indent + partial[i] + ",\n"; if (partial.length > 0) { // Remove trailing comma result = result.slice(0, result.length - 2) + "\n"; } result += cumulativeIndent + "}"; return result; } default: return "null"; } } if (indent === undefined) indent = " "; if (objectMaxDepth === undefined) objectMaxDepth = 0; if (arrayMaxLength === undefined) arrayMaxLength = 50; // Matches characters that must be escaped var escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; // The replacement characters var replacements = { "\b": "\\b", "\t": "\\t", "\n": "\\n", "\f": "\\f", "\r": "\\r", "\"": "\\\"", "\\": "\\\\" }; // A list of all the objects that were seen (used to avoid recursion) var values = []; // The path of an object in the JSON object, with indexes corresponding to entries in the // "values" variable. var paths = []; return toString("root", object, "", 0); }; 

    Penso che il formato che stai usando non sia adatto a fare ciò che vuoi. Ottenendo tutti i dati contenuti nell’object della finestra in una singola stringa JSON, si supponga di conservare questa stringa in memoria durante la sua creazione causando problemi incontrati.

    Hai bisogno di un formato che ti dia la possibilità di inviare i dati mentre vengono analizzati dall’object della finestra per liberare memoria al volo. Per questo, dovresti usare qualcosa come CSV, Text o VarStream ( https://github.com/nfroidure/VarStream ).

    Puoi anche eseguire iterazione dell’object e provare a JSON.stringify in un try … catch. Se la prova è un successo, si invia il file JSON, se fallisce, si itera attraverso le proprietà dell’object con lo stesso try … catch ecc … Ma è una brutta soluzione che non incoraggio a usare.

    Ecco il mio spasificatore per rimuovere JSON per la registrazione sicura di oggetti con riferimenti ciclici, elementi DOM, ambiti angolari o finestra.

    Previene TypeError: Converting circular structure to JSON sostituendo i riferimenti circolari con ”.

    Previene RangeError: Maximum call stack size exceeded . Tuttavia, si consiglia di utilizzare maxDepth o filterObjects in ogni caso, poiché la serializzazione di oggetti molto profondi costa sia tempo che spazio, il che potrebbe ridurre la sua usabilità per la registrazione generale e persino rendere il browser di test disconnettere quando viene utilizzato nei test.

    opzionalmente:

    • limita la profondità dell’ispezione dell’object (non ancora implementata),
    • filtra oggetti (come finestra, framework di test, runner di test),
    • filtri elementi DOM,
    • filtra gli oggetti $ object angular.

    Fonte + commenti: https://gist.github.com/iki/9371373

     (function (input, level) { if (!input) return input; level = level || 4; var objectsAlreadySerialized = [input], objDepth = [input]; return JSON.stringify(input, function (key, value) { if (key) { if (typeof value === 'object') { if (objectsAlreadySerialized.indexOf(value) !== -1) return undefined; objectsAlreadySerialized.push(value); } if (objDepth.indexOf(this) === -1) objDepth.push(this); else while(objDepth[objDepth.length-1] !== this) objDepth.pop(); if (objDepth.length > level) return undefined; } return value; }); })(window, 6) 

    Potresti semplicemente mantenere la profondità a cui sei:

     function stringify(obj, currentDepth, maxDepth) { if (currentDepth == maxDepth) return '[Warning: max level reached]' var str = '{'; for (var key in obj) { str += key + ': ' + typeof obj == 'object' ? stringify(obj[key], currentDepth + 1, maxDepth) : obj[key]; } return str + '}' } 

    (solo un esempio – ovviamente questo frammento non rileva la ricorsione)