Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JSON.stringify deep objects

I need a function building a JSON valid string from any argument but :

  • avoiding recursivity problem by not adding objects twice
  • avoiding call stack size problem by truncating past a given depth

Generally it should be able to process big objects, at the cost of truncating them.

As reference, this code fails :

var json = JSON.stringify(window); 

Avoiding recursivity problem is simple enough :

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

But for now, apart copying and changing Douglas Crockford's code to keep track of the depth, I didn't find any way to avoid stack overflow on very deep objects like window or any event. Is there a simple solution ?

like image 673
Denys Séguret Avatar asked Dec 13 '12 13:12

Denys Séguret


People also ask

Does JSON Stringify deep copy?

However, there is one caveat to using this approach: JSON. stringify() does not copy functions. The function will not be available in the copied object. Thus, this method achieves deep copy only if there is no function within the object.

Does JSON Stringify work on objects?

The JSON.stringify() method converts a JavaScript object or value to a JSON string, optionally replacing values if a replacer function is specified or optionally including only the specified properties if a replacer array is specified.

Is it bad to use JSON Stringify?

It`s ok to use it with some primitives like Numbers, Strings or Booleans. As you can see, you can just lose unsupported some data when copying your object in such a way. Moreover, JavaScript won`t even warn you about that, because calling JSON. stringify() with such data types does not throw any error.


2 Answers

I did what I initially feared I'll have to do : I took Crockford's code and modified it for my needs. Now it builds JSON but handles

  • cycles
  • too deep objects
  • too long arrays
  • exceptions (accessors that can't legally be accessed)

In case anybody needs it, I made a GitHub repository : JSON.prune on GitHub

Here is the code :

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

An example of what can be done :

var json = JSON.pruned(window); 

Note: Contrary to the code in this answer, the GitHub repository is updated when needed (documentation, compatibility, use as module in commonjs or node, specific serializations, etc.). It's a good idea to start from the repository if you need this pruning feature.

like image 90
Denys Séguret Avatar answered Oct 13 '22 00:10

Denys Séguret


If you're using Node.js you can use util.inspect, which takes a depth argument.

like image 24
Rhys van der Waerden Avatar answered Oct 12 '22 23:10

Rhys van der Waerden