2012-12-13 81 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

尽管此代码可能会回答问题,但提供有关如何解决问题和/或为何解决问题的其他上下文将提高​​答案的长期价值。 –