JSON.stringify, avoid TypeError: Converting circul

2018-12-31 05:07发布

I have a big object I want to convert to JSON and send. However it has circular structure. I want to toss whatever circular references exist and send whatever can be stringified. How do I do that?

Thanks.

var obj = {
  a: "foo",
  b: obj
}

I want to stringify obj into:

{"a":"foo"}

20条回答
泛滥B
2楼-- · 2018-12-31 05:47

I found circular-json library on github and it worked well for my problem.

Some good features I found useful:

  • Supports multi-platform usage but I only tested it with node.js so far.
  • API is same so all you need to do is include and use it as a JSON replacement.
  • It have it's own parsing method so you can convert the 'circular' serialized data back to object.
查看更多
君临天下
3楼-- · 2018-12-31 05:48

just do

npm i --save circular-json

then in your js file

const JSON = require('circular-json');
...
const json = JSON.stringify(obj);

You could also do

const CircularJSON = require('circular-json');

https://github.com/WebReflection/circular-json

NOTE: I have nothing to do with this package. But I do use it for this.

查看更多
春风洒进眼中
4楼-- · 2018-12-31 05:49

I know this is an old question, but I'd like to suggest an NPM package I've created called smart-circular, which works differently from the other ways proposed. It's specially useful if you're using big and deep objects.

Some features are:

  • Replacing circular references or simply repeated structures inside the object by the path leading to its first occurrence (not just the string [circular]);

  • By looking for circularities in a breadth-first search, the package ensures this path is as small as possible, which is important when dealing with very big and deep objects, where the paths can get annoyingly long and difficult to follow (the custom replacement in JSON.stringify does a DFS);

  • Allows personalised replacements, handy to simplify or ignore less important parts of the object;

  • Finally, the paths are written exactly in the way necessary to access the field referenced, which can help you debugging.

查看更多
其实,你不懂
5楼-- · 2018-12-31 05:51

I wonder why nobody posted the proper solution from MDN page yet...

const getCircularReplacer = () => {
  const seen = new WeakSet();
  return (key, value) => {
    if (typeof value === "object" && value !== null) {
      if (seen.has(value)) {
        return;
      }
      seen.add(value);
    }
    return value;
  };
};

JSON.stringify(circularReference, getCircularReplacer());

Seen values should be stored in a set, not in array (replacer gets called on every element) and there is no need to try JSON.stringify each element in the chain leading to a circular reference.

Like in the accepted answer, this solution removes all repeating values, not just the circular ones. But at least it does not have exponential complexity.

查看更多
墨雨无痕
6楼-- · 2018-12-31 05:52

I really liked Trindaz's solution - more verbose, however it had some bugs. I fixed them for whoever likes it too.

Plus, I added a length limit on my cache objects.

If the object I am printing is really big - I mean infinitely big - I want to limit my algorithm.

JSON.stringifyOnce = function(obj, replacer, indent){
    var printedObjects = [];
    var printedObjectKeys = [];

    function printOnceReplacer(key, value){
        if ( printedObjects.length > 2000){ // browsers will not print more than 20K, I don't see the point to allow 2K.. algorithm will not be fast anyway if we have too many objects
        return 'object too long';
        }
        var printedObjIndex = false;
        printedObjects.forEach(function(obj, index){
            if(obj===value){
                printedObjIndex = index;
            }
        });

        if ( key == ''){ //root element
             printedObjects.push(obj);
            printedObjectKeys.push("root");
             return value;
        }

        else if(printedObjIndex+"" != "false" && typeof(value)=="object"){
            if ( printedObjectKeys[printedObjIndex] == "root"){
                return "(pointer to root)";
            }else{
                return "(see " + ((!!value && !!value.constructor) ? value.constructor.name.toLowerCase()  : typeof(value)) + " with key " + printedObjectKeys[printedObjIndex] + ")";
            }
        }else{

            var qualifiedKey = key || "(empty key)";
            printedObjects.push(value);
            printedObjectKeys.push(qualifiedKey);
            if(replacer){
                return replacer(key, value);
            }else{
                return value;
            }
        }
    }
    return JSON.stringify(obj, printOnceReplacer, indent);
};
查看更多
孤独寂梦人
7楼-- · 2018-12-31 05:53
var a={b:"b"};
a.a=a;
JSON.stringify(preventCircularJson(a));

evaluates to:

"{"b":"b","a":"CIRCULAR_REFERENCE_REMOVED"}"

with the function:

/**
 * Traverses a javascript object, and deletes all circular values
 * @param source object to remove circular references from
 * @param censoredMessage optional: what to put instead of censored values
 * @param censorTheseItems should be kept null, used in recursion
 * @returns {undefined}
 */
function preventCircularJson(source, censoredMessage, censorTheseItems) {
    //init recursive value if this is the first call
    censorTheseItems = censorTheseItems || [source];
    //default if none is specified
    censoredMessage = censoredMessage || "CIRCULAR_REFERENCE_REMOVED";
    //values that have allready apeared will be placed here:
    var recursiveItems = {};
    //initaite a censored clone to return back
    var ret = {};
    //traverse the object:
    for (var key in source) {
        var value = source[key]
        if (typeof value == "object") {
            //re-examine all complex children again later:
            recursiveItems[key] = value;
        } else {
            //simple values copied as is
            ret[key] = value;
        }
    }
    //create list of values to censor:
    var censorChildItems = [];
    for (var key in recursiveItems) {
        var value = source[key];
        //all complex child objects should not apear again in children:
        censorChildItems.push(value);
    }
    //censor all circular values
    for (var key in recursiveItems) {
        var value = source[key];
        var censored = false;
        censorTheseItems.forEach(function (item) {
            if (item === value) {
                censored = true;
            }
        });
        if (censored) {
            //change circular values to this
            value = censoredMessage;
        } else {
            //recursion:
            value = preventCircularJson(value, censoredMessage, censorChildItems.concat(censorTheseItems));
        }
        ret[key] = value

    }

    return ret;
}
查看更多
登录 后发表回答