2012-12-13 18 views
42

muszę funkcję budowanie JSON prawidłowy ciąg z dowolnego argumentu ALE:JSON.stringify głębokie obiekty

  • unikając problemu recursivity przez nie dodając dwukrotnie
  • przedmioty unikając problemu rozmiar stosu wywołań przez obcinanie przeszłość danej głębokości

Generalnie powinna być w stanie przetwarzać duże obiekty, kosztem ich obcięcia.

Jako odniesienie, ten kod nie powiedzie się:

var json = JSON.stringify(window); 

Unikanie problemu recursivity jest dość prosta:

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; 
}); 

Ale teraz, oprócz kopiowania i zmiany Douglas Crockford's code śledzić głębokości, ja nie zrobił nie znajdziesz sposobu, aby uniknąć przepełnienia stosu na bardzo głębokich obiektach, takich jak window lub dowolnej . Czy istnieje proste rozwiązanie?

+0

Co masz na myśli przez "bardzo głębokiej obiektu"? Czy naprawdę istnieją obiekty, które przechodzą (bez "właściwości rekursywnych") poza rozmiar stosu? – Bergi

+0

yes: 'window' na przykład. Możliwe, że w moim kodzie jest błąd, a prawdziwym problemem jest rekurencyjność, ponieważ 'okno' jest zarówno rekurencyjne, jak i głębokie (dlatego podałem mój kod). –

+0

Hm, dostaję 'z pamięci (sterty)' podczas próby twojego skryptu w 'oknie': -/ – Bergi

Odpowiedz

79

Zrobiłem to, co początkowo obawiali będę musiał do: Wziąłem kod Crockforda i zmodyfikowałem go dla moich potrzeb.Teraz buduje JSON, ale obsługuje

  • cykle
  • zbyt głębokie obiekty
  • zbyt długie tablice
  • wyjątki (akcesorów, które nie mogą być legalnie udostępniony)

W przypadku ktokolwiek potrzebuje , Stworzyłem repozytorium GitHub: JSON.prune on GitHub

Oto kod:

// 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); 
    }; 

}()); 

Przykładem tego, co można zrobić:

var json = JSON.pruned(window); 

Uwaga: W przeciwieństwie do kodu w tej odpowiedzi, GitHub repository jest aktualizowana w razie potrzeby (dokumentacja, kompatybilność, należy użyć jako moduł w commonjs lub węzeł, specjalne serializacje itp.). Dobrym pomysłem jest wystartować z repozytorium, jeśli potrzebujesz funkcji przycinania.

+0

@SarahManning JSON oczywiście nie zawiera funkcji. Jeśli chcesz je przekształcić do postaci szeregowej, możesz to zrobić za pomocą JSON.prune: https://github.com/Canop/JSON.prune#example-4-function-serialization –

+0

Każdy, kto potrzebuje funkcji, zobacz ten numer http: // github .com/Canop/JSON.prune/issues/5 –

+1

Zrobiłeś historię tutaj, kolega –

0

Myślę, że format, którego używasz, jest nieodpowiedni do robienia tego, co chcesz. Pobranie wszystkich danych zawartych w obiekcie okna do pojedynczego łańcucha JSON pozwala przypisać ten ciąg w pamięci podczas budowania jej powodując napotkane problemy.

Potrzebujesz formatu umożliwiającego przesyłanie danych, gdy jest on parsowany z obiektu okna, aby zwolnić pamięć w locie. W tym celu powinieneś użyć czegoś takiego jak CSV, Text lub VarStream (https://github.com/nfroidure/VarStream).

Można również powtórzyć cały obiekt i spróbować JSON.stringify je w próbie ... catch. Jeśli próba zakończy się sukcesem, wyślesz plik JSON, jeśli się nie powiedzie, to iterujesz po właściwościach obiektu z tą samą próbą ... catch itd ... Ale jest to brzydkie obejście, którego nie zachęcam do użycia.

-5

Można po prostu zachować głębię jesteś w:

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

(tylko przykład- oczywiście ten fragment nie wykrywa rekurencji)

+1

Nie zbuduje ciągów JSON z żadnego obiektu. –

4

można po prostu użyć Censor funkcję jak w poniższym przykładzie:

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); 

Wyjście jest {"week":45,"month":7}.

Tak jak w twoim przykładzie, musisz zwrócić undefined, jeśli masz obiekt wartości, który jest oknem.

4

mam poprawione @ odpowiedź dystroy, dodając:

  • wcięcia dla podrzędnych właściwości.
  • Wskazanie, gdzie wskazują odwołania kołowe.
/** 
* 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

Oto mój stringifier do pozbawionego JSON dla bezpiecznego pozyskiwania obiektów z cyklicznych odwołań, elementów DOM, kątowych zakresów lub okna.

Zapobiega TypeError: Converting circular structure to JSON zastępując odwołania kołowe odwołaniami "".

Zapobiega RangeError: Maximum call stack size exceeded. Zaleca się jednak stosowanie funkcji maxDepth lub filterObjects, ponieważ szeregowanie bardzo głębokich obiektów kosztuje zarówno czas, jak i przestrzeń, co może obniżyć jego użyteczność w przypadku ogólnego rejestrowania, a nawet spowodować, że testowa przeglądarka rozłączy się, gdy zostanie użyta w testach.

Opcjonalnie:

  • granice obiekt głębokość kontroli (niezaimplementowane)
  • filtry obiektów (na przykład okna, w ramach test, test płozy)
  • elementów filtrów DOM,
  • filtry kątowe atrybut obiektu $.

źródło + komentarze: https://gist.github.com/iki/9371373

7

Jeśli używasz node.js można użyć util.inspect, która przyjmuje argument głębokości.

-3

to może działać:

(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

Podczas gdy ten kod może odpowiedzieć na pytanie, podanie dodatkowego kontekstu dotyczącego tego, w jaki sposób i/lub dlaczego rozwiązuje problem, poprawiłoby długoterminową wartość odpowiedzi. –

Powiązane problemy