2012-12-13 12 views
42

ho bisogno di una funzione di costruzione di una stringa valida JSON da alcun argomento ma:JSON.stringify oggetti profondi

  • evitando problema ricorsività non aggiungendo oggetti due volte
  • evitando problema stack chiamata troncando passato una data profondità

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

come riferimento, questo codice fallisce:

var json = JSON.stringify(window); 

Evitare problema 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 il momento, a parte la copia e la modifica Douglas Crockford's code per tenere traccia della profondità, I didn trovare un modo per evitare l'overflow dello stack su oggetti molto profondi come window o qualsiasi event. C'è una soluzione semplice?

+0

Cosa si intende per "oggetto molto profondo"? Ci sono davvero oggetti che vanno (senza "proprietà ricorsive") oltre le dimensioni dello stack? – Bergi

+0

sì: 'window' per esempio. È possibile che ci sia un bug nel mio codice e che il vero problema sia la ricorsività, dato che 'window' è sia ricorsiva che profonda (è per questo che ho dato il mio codice). –

+0

Hm, sto ricevendo una 'memoria esaurita '(heap) quando provo il tuo script su' window': -/ – Bergi

risposta

79

ho fatto quello che ho inizialmente temuto che dovrò do: ho preso il codice di Crockford e l'ho modificato per i miei bisogni.Ora si costruisce JSON ma gestisce

  • cicli
  • oggetti
  • troppo profonde
  • troppo lunghi array
  • eccezioni (di accesso che non possono essere legalmente accessibili)

Nel caso qualcuno ne ha bisogno , Ho creato un repository GitHub: JSON.prune on GitHub

Questo è 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 GitHub repository viene aggiornata quando necessario (documentazione, la compatibilità, utilizzare come modulo commonjs o nodo, serializzazioni specifiche, ecc.). È una buona idea partire dal repository se hai bisogno di questa funzione di eliminazione.

+0

@SarahManning JSON non include le funzioni, ovviamente. Se si desidera serializzare loro, è possibile farlo con JSON.prune però: https://github.com/Canop/JSON.prune#example-4-function-serialization –

+0

funzioni chiunque abbia bisogno di vedere questo problema http: // GitHub .com/Canop/JSON.prune/Problematiche/5 –

+1

Hai fatto la storia qui accoppiano –

0

Penso che il formato che stai usando sia solo improprio nel fare ciò che vuoi. Ottenendo tutti i dati contenuti nell'oggetto della finestra in una singola stringa JSON, si supponga di conservare questa stringa in memoria durante la sua creazione causando problemi incontrati.

È necessario un formato che permetta di inviare dati mentre vengono analizzati dall'oggetto della finestra per liberare memoria al volo. Per questo, dovresti usare qualcosa come CSV, Text o VarStream (https://github.com/nfroidure/VarStream).

Si può anche eseguire iterazione dell'oggetto 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'oggetto con lo stesso try ... catch ecc ... Ma è una brutta soluzione che non incoraggio a usare.

-5

Si può solo mantenere la profondità Sei in:

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 + '}' 
} 

(esempio-solo, ovviamente, questo frammento non rileva ricorsione)

+1

Questo non costruirà una stringa JSON da alcun oggetto. –

4

si può semplicemente utilizzare una funzione Censor come nell'esempio qui sotto:

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'uscita è {"week":45,"month":7}.

Così come per l'esempio, è necessario tornare indefinito se si dispone di un oggetto valore, che è una finestra.

4

ho rivisto @ risposta di dystroy, aggiungendo:

  • rientro per sub-proprietà.
  • Un'indicazione di dove i riferimenti circolari puntano a.
/** 
* 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 <a href="https://github.com/Canop/JSON.prune/blob/master/JSON.prune.js">https://github.com/Canop/JSON.prune/blob/master/JSON.prune.js</a> 
    * 
    * @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 (e.g. 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); 
}; 
0

Ecco la mia stringifier a spogliato JSON per la registrazione sicura di oggetti con riferimenti ciclici, elementi DOM, cannocchiali 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 utilizzato nei test.

Facoltativamente:

  • limiti oggetto profondità ispezione (non ancora implementato),
  • filtri oggetti (come finestra, quadro test, test runner),
  • elementi filtri DOM,
  • filtri angolari oggetto $ attributi.

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

7

Se stai usando Node.js è possibile utilizzare util.inspect, che prende un argomento di profondità.

-3

Questo può funzionare:

(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) 
+1

Anche se questo codice può rispondere alla domanda, fornendo un contesto aggiuntivo per quanto riguarda come e/o perché risolve il problema migliorerebbe valore a lungo termine della risposta. –

Problemi correlati