2012-12-13 76 views
42

我需要一個函數構建從任何說法,但一個JSON有效的字符串:JSON.stringify深對象

  • 通過不添加對象兩次
  • 通過截取過去給定的深度,避免調用堆棧的大小問題,避免遞歸性問題

一般來說,它應該能夠處理大對象,並以截斷它們爲代價。

作爲參考,該代碼失敗:

var json = JSON.stringify(window); 

避免遞歸性的問題很簡單:

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

但是現在,除了複製和改變Douglas Crockford's code保持深度的跟蹤,我沒找不到任何方法來避免像window或任何event這樣的非常深的對象上的堆棧溢出。有一個簡單的解決方案嗎?

+0

你是什麼意思是 「非常深的對象」 嗎?是否有真正的對象(沒有「遞歸屬性」)超出堆棧大小? – Bergi

+0

是:例如「窗口」。可能在我的代碼中存在一個錯誤,但真正的問題是遞歸,因爲'window'既是遞歸的又是深層的(這就是爲什麼我給出了我的代碼)。 –

+0

嗯,當我在'window'上試用腳本時,我得到了'(堆)內存': -/ – Bergi

回答

79

我做了我最初擔心我得做:我採取了Crockford的代碼,並根據我的需要進行了修改。現在,它建立JSON但處理

  • 週期
  • 太深對象
  • 太長陣列
  • 異常(即法律不能訪問存取)

萬一有人需要它,我做了一個GitHub倉庫:JSON.prune on GitHub

這裏是代碼:

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

}()); 

什麼可以做一個例子:

var json = JSON.pruned(window); 

注:相反,在這個答案的代碼,需要時GitHub repository更新(文檔,兼容性,CommonJS的使用爲模塊或節點,特定序列化等)。如果您需要此修剪功能,從存儲庫啓動是一個好主意。

+0

@SarahManning JSON當然不包括函數。如果你想序列化它們,你可以使用JSON.prune來完成它:https://github.com/Canop/JSON.prune#example-4-function- serialization –

+0

任何需要功能的人都會看到這個問題http:// github .com/Canop/JSON.prune/issues/5 – 2015-12-22 01:07:43

+1

您在此創建歷史記錄隊友 –

0

我認爲你使用的格式只是做不到你想要的。將窗口對象中包含的所有數據都包含到單個JSON字符串中,假設在構建她時遇到問題時將此字符串保留在內存中。

您需要一種格式才能發送數據,因爲它是從窗口對象中解析出來的,以便實時釋放內存。對於這個問題,你應該使用像CSV,文本或VarStream(https://github.com/nfroidure/VarStream)。

你也可以遍歷對象並嘗試JSON.stringify他們在try ... catch。如果嘗試成功,則發送JSON文件,如果失敗,則使用相同的try ... catch等方法遍歷對象屬性...但這是一個醜陋的解決方法,我不鼓勵您使用。

-5

你可以只保留你在深度:

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

(只是例 - 顯然這個片段沒有檢測遞歸)

+1

這不會從任何對象構建JSON字符串。 –

4

您可以簡單地用一個Censor功能就像下面的例子:

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

輸出是{"week":45,"month":7}

至於你的例子,如果你有一個值對象,這是一個窗口,你必須返回undefined。

4

我已經修訂@ dystroy的回答,並補充說:

  • 縮進的子屬性。
  • 指示循環引用指向的位置。
/** 
* 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

這裏是我的stringifier到剝離JSON用於循環引用,DOM元素,角範圍或窗口對象的安全記錄。

通過用''替換循環引用來防止TypeError: Converting circular structure to JSON

防止RangeError: Maximum call stack size exceeded。 但是,無論如何,建議使用maxDepth或filterObjects,因爲序列化非常深的對象既耗費時間又耗費空間,這可能會降低其對通用日誌記錄的可用性,甚至會使測試瀏覽器在測試中斷開連接。

任選地:

  • 限制對象檢測深度(尚未實現),
  • 濾波器對象(如窗口,測試框架,測試運行),
  • 濾波器DOM元素,
  • 濾波器的角對象$屬性。

來源+評論:https://gist.github.com/iki/9371373

7

如果您使用Node.js的,你可以使用util.inspect,這需要一個深度參數。

-3

這可能工作:

(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

儘管此代碼可能會回答問題,但提供有關如何解決問題和/或爲何解決問題的其他上下文將提高​​答案的長期價值。 –