Stringfy,避免类型错误: 将循环结构转换为 JSON

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"}

转载于:https://stackoverflow.com/questions/11616630/json-stringify-avoid-typeerror-converting-circular-structure-to-json

csdnceshi59
ℙℕℤℝ Late to the party but there is a github project to handle this.
6 年多之前 回复
csdnceshi78
程序go possible duplicate of serializing object that contains cyclic object value
8 年多之前 回复
csdnceshi64
游.程 something like this?
8 年多之前 回复
csdnceshi71
Memor.の Could you please post a sample object with a circular reference that you'd like to parse ?
8 年多之前 回复

19个回答

Use JSON.stringify with a custom replacer. For example:

// Demo: Circular reference
var o = {};
o.o = o;

// Note: cache should not be re-used by repeated calls to JSON.stringify.
var cache = [];
JSON.stringify(o, function(key, value) {
    if (typeof value === 'object' && value !== null) {
        if (cache.indexOf(value) !== -1) {
            // Duplicate reference found
            try {
                // If this value does not reference a parent it can be deduped
                return JSON.parse(JSON.stringify(value));
            } catch (error) {
                // discard key if value cannot be deduped
                return;
            }
        }
        // Store value in our collection
        cache.push(value);
    }
    return value;
});
cache = null; // Enable garbage collection

The replacer in this example is not 100% correct (depending on your definition of "duplicate"). In the following case, a value is discarded:

var a = {b:1}
var o = {};
o.one = a;
o.two = a;
// one and two point to the same object, but two is discarded:
JSON.stringify(o, ...);

But the concept stands: Use a custom replacer, and keep track of the parsed object values.

csdnceshi68
local-host Small addition helped me reduce useless for debug-purpose info: if( key.startsWith('_') ) { return; }
大约 2 年之前 回复
csdnceshi62
csdnceshi62 This is correct, but there is a better solution using Map! We can make this more performant using a hashmap, see: stackoverflow.com/a/48254637/1223975
接近 3 年之前 回复
weixin_41568134
MAO-EYE this is a good solution!
3 年多之前 回复
csdnceshi75
衫裤跑路 I was using this function without flushing the cache, and this gave very unexpected results. Saving the next guy some headache, i took the liberty to make the cache var local.
3 年多之前 回复
csdnceshi51
旧行李 The linear check is worthy of mocking no matter who writes it. ;-) See the code related to the objects array and the comment above it: «This is a hard way, linear search that will get slower as the number of unique objects grows.»
大约 5 年之前 回复
csdnceshi51
旧行李 Presumably one of the global objects you're stringifying has a toJSON that throws. FYI: By passing in window you'll be stringifying every global variable. Probably not a good idea.
大约 5 年之前 回复
csdnceshi56
lrony* using your stringifier and passing in window I still get Error: Permission denied to access property "toJSON" , it's funny cause it works with any other cyclic test obj but not window or 'document! could there be that there is an intentional limitation placed on the JSON.stringify` that inhibits it from executing certain objects?
大约 5 年之前 回复
csdnceshi60
℡Wang Yan Mock Crockford at your peril. ;^) Where do you see a bogus check? (Apologies for comment-jacking. I'll delete this one later)
大约 5 年之前 回复
csdnceshi51
旧行李 Since making that comment, I've taken the JSON Stringify Safe lib from Isaac and rewrote it: github.com/isaacs/json-stringify-safe. I'm not sure what the supposedly live-fire robust Crockford code does. It looks overly complicated and seems to do the same poor linear check that I warned against above.
大约 5 年之前 回复
csdnceshi60
℡Wang Yan Pretty sure Rob's just concisely demonstrating custom replacers, which he's done very well. Preston's comment points to some Crockfordian code that is live-fire robust, if that's what you're after. But the concept is the same.
大约 5 年之前 回复
weixin_41568110
七度&光 Note that IE 8 does not support Array.prototype.indexOf(). You may need to polyfill. See developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
5 年多之前 回复
weixin_41568208
北城已荒凉 The GC concern here is arguably redundant. If this is run as a single script then script immediately terminates. If this is encapsulated inside a function for implementation then cache will be unreachable developer.mozilla.org/en-US/docs/Web/JavaScript/…
接近 6 年之前 回复
csdnceshi51
旧行李 Don't use this indeed. It strips out every duplicate object (which isn't a circular dependency) and is hellishly slow by checking every seen object.
大约 6 年之前 回复
weixin_41568131
10.24 "The replacer in this example is not 100% correct (depending on your definition of "duplicate"). But the concept stands: Use a custom replacer, and keep track of the parsed object values."
6 年多之前 回复
csdnceshi54
hurriedly% this is wrong because it will skip the second appearance of objects that are contained twice, even if not in a really cyclic structure. var a={id:1}; JSON.stringify([a,a]);
6 年多之前 回复
weixin_41568131
10.24 Serializing DOM is usually meaningless. However, if you can think of a meaningful serialization method for your purposes, then you could try to add a custom serialized to DOM objects: Node.prototype.toJSON = function() { return 'whatever you think that is right'; }; (if you want anything more generic/specific, just try anything in the prototype tree: HTMLDivElement implements HTMLElement implements Element implements Node implements EventTarget; note: this may be browser-dependent, the previous tree is true for Chrome)
6 年多之前 回复
csdnceshi67
bug^君 I'm getting the same error trying to stringify an instance of CKEditor. It saves inside the DOM element associated, so, I can't stringify with this approach
6 年多之前 回复
weixin_41568131
10.24 Trying to serialize a non-serializable object won't result in anything useful... What would you expect from serializing window?
6 年多之前 回复
csdnceshi66
必承其重 | 欲带皇冠 JSON.stringify(window, ...) on this page breaks with the exception: InvalidStateError: Failed to read the 'selectionDirection' property from 'HTMLInputElement': The input element's type ('hidden') does not support selection.
6 年多之前 回复
csdnceshi74
7*4 By the way - I ran into an interesting scenario. Someone printed the attrs parameters in an AngularJS directive. I think this object is just too big to be printed. Chrome gives up after 2582 keys. So I added to my solution a cache length limit.
7 年多之前 回复
csdnceshi52
妄徒之命 You should be checking typeof value not typeof cache
8 年多之前 回复
weixin_41568131
10.24 What's the bug? I'll gladly fix the answer, if there are any inaccuracies in it.
8 年多之前 回复
csdnceshi52
妄徒之命 There was a small bug in your code but the overall idea I understand, thanks!
8 年多之前 回复

In Node.js, you can use util.inspect(object). It automatically replaces circular links with "[Circular]".


Albeit being built-in (no installation is required), you must import it

import * as util from 'util' // has no default export
import { inspect } from 'util' // or directly
// or 
var util = require('util')
To use it, simply call
console.log(util.inspect(myObject))

Also be aware that you can pass options object to inspect (see link above)

inspect(myObject[, options: {showHidden, depth, colors, showProxy, ...moreOptions}])



Please, read and give kudos to commenters below...

csdnceshi63
elliott.david Albeit being built-in, you must import it with either import * as util from 'util' or var util = require('util'). To use it, simply call console.log(util.inspect(myObject)). Also be aware that you can pass additional parameters to inspect(myObject[, options: {showHidden, depth, colors, showProxy, ...otherOptions}]).
2 年多之前 回复
csdnceshi72
谁还没个明天 This is much better than mucking around with checking types. Why can't stringify just work like this? If it knows there's a circular reference, why can't it just be told to ignore it???
2 年多之前 回复
csdnceshi53
Lotus@ Do note that util.inspect does not always return valid JSON. For example util.inspect(new Error('This is an example error'))
接近 4 年之前 回复
csdnceshi62
csdnceshi62 Don't be a dunce like me, its just obj_str = util.inspect(thing), NOT <s>garbage_str = JSON.stringify(util.inspect(thing))</s>
接近 4 年之前 回复
weixin_41568208
北城已荒凉 it is built-in, but you still have to load the module var util = require('util');
大约 5 年之前 回复
weixin_41568126
乱世@小熊 One more tip about this: I use util.inspect with the following arguments, console.log(util.inspect(object, false, 10, true)); the second argument is for setting showHiddenProperties to false, the 10 is to tell it to dig into the object up to 10 levels deep and the last 'true' is to ask it to colorize the output. reference: docs.nodejitsu.com/articles/getting-started/…
大约 5 年之前 回复
csdnceshi68
local-host Note that if for some reason you need to use JSON.stringify, you CANNOT USE util.inspect, because it outputs a string, and not an object.
5 年多之前 回复
csdnceshi77
狐狸.fox This was a great suggestion! Thanks so much! I never thought seeing a console logged object would be so satisfying, but it was as if you broke all the objs open for me now to work with.
接近 6 年之前 回复
csdnceshi54
hurriedly% console.log(util.inspect(obj))
接近 6 年之前 回复
csdnceshi69
YaoRaoLov It's a shame I can't upvote more. util.inspect can be used from browserify too!
6 年多之前 回复
csdnceshi75
衫裤跑路 util is a built-in module, you do not have to install it.
6 年多之前 回复
csdnceshi61
derek5. Just for good measure: To Install: "npm install util" In your code: "var util = require('util');"
接近 7 年之前 回复

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);
};
csdnceshi71
Memor.の // browsers will not print more than 20K - But you put limit as 2k. Perhaps change for the future?
4 年多之前 回复
csdnceshi76
斗士狗 thanks!
5 年多之前 回复
csdnceshi55
~Onlooker I edited the code to include the null check.
大约 7 年之前 回复
csdnceshi61
derek5. I hit the error when I tried it on a mouse event object.
大约 7 年之前 回复
csdnceshi76
斗士狗 I will gladly add it. just let me know what is nullable as I did experience any problems so far.
大约 7 年之前 回复
csdnceshi61
derek5. You're missing a null check on this line : return "(see " + (!!value.constructor ? value.constructor.name.toLowerCase() : typeof(value)) + " with key " + printedObjectKeys[printedObjIndex] + ")";
大约 7 年之前 回复

The second argument to JSON.stringify() also allows you to specify an array of key names that should be preserved from every object it encounters within your data. This may not work for all use cases, but is a much simpler solution.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify

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

var json = JSON.stringify(obj, ['a']);
console.log(json);
// {"a":"foo"}

Note: Strangely, the object definition from OP does not throw a circular reference error in the latest Chrome or Firefox. The definition in this answer was modified so that it did throw an error.


I'd recommend checking out json-stringify-safe from @isaacs-- it's used in NPM.

BTW- if you're not using Node.js, you can just copy and paste lines 4-27 from the relevant part of the source code.

To install:

$ npm install json-stringify-safe --save

To use:

// Require the thing
var stringify = require('json-stringify-safe');

// Take some nasty circular object
var theBigNasty = {
  a: "foo",
  b: theBigNasty
};

// Then clean it up a little bit
var sanitized = JSON.parse(stringify(theBigNasty));

This yields:

{
  a: 'foo',
  b: '[Circular]'
}

Note that, just like with the vanilla JSON.stringify function as @Rob W mentioned, you can also customize the sanitization behavior by passing in a "replacer" function as the second argument to stringify(). If you find yourself needing a simple example of how to do this, I just wrote a custom replacer which coerces errors, regexps, and functions into human-readable strings here.

csdnceshi63
elliott.david that bigNasty tho
大约 2 年之前 回复

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.

Note that there is also a JSON.decycle method implemented by Douglas Crockford. See his cycle.js. This allows you to stringify almost any standard structure:

var a = [];
a[0] = a;
a[1] = 123;
console.log(JSON.stringify(JSON.decycle(a)));
// result: '[{"$ref":"$"},123]'.

You can also recreate original object with retrocycle method. So you don't have to remove cycles from objects to stringify them.

However this will not work for DOM Nodes (which are typical cause of cycles in real life use-cases). For example this will throw:

var a = [document.body];
console.log(JSON.stringify(JSON.decycle(a)));

I've made a fork to solve that problem (see my cycle.js fork). This should work fine:

var a = [document.body];
console.log(JSON.stringify(JSON.decycle(a, true)));

Note that in my fork JSON.decycle(variable) works as in the original and will throw an exception when the variable contain DOM nodes/elements.

When you use JSON.decycle(variable, true) you accept the fact that the result will not be reversible (retrocycle will not re-create DOM nodes). DOM elements should be identifiable to some extent though. For example if a div element has an id then it will be replaced with a string "div#id-of-the-element".

csdnceshi67
bug^君 : thanks a lot!
大约 2 年之前 回复
csdnceshi55
~Onlooker thanks! works well!
4 年多之前 回复
weixin_41568110
七度&光 true makes stringifyNodes option true in the fork. This will dump e.g. div with id="some-id" to string: div#some-id. You will avoid some problems, but you won't to be able to fully retro-cycle.
接近 5 年之前 回复
csdnceshi57
perhaps? This is what I was looking for. JSON.decycle(a, true) what happens when you pass true as a parameter to decycle function.
接近 5 年之前 回复
weixin_41568126
乱世@小熊 You saved the day !
5 年多之前 回复
weixin_41568110
七度&光 I can have a look if you provide your code on the Fiddle or add an issue on Github: github.com/Eccenux/JSON-js/issues
6 年多之前 回复
csdnceshi56
lrony* Both his code and yours give me a "RangeError: Maximum call stack size exceeded" when I use them.
6 年多之前 回复

For future googlers searching for a solution to this problem when you don't know the keys of all circular references, you could use a wrapper around the JSON.stringify function to rule out circular references. See an example script at https://gist.github.com/4653128.

The solution essentially boils down to keeping a reference to previously printed objects in an array, and checking that in a replacer function before returning a value. It's more constrictive than only ruling out circular references, because it also rules out ever printing an object twice, one of the side affects of which is to avoid circular references.

Example wrapper:

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

    function printOnceReplacer(key, value){
        var printedObjIndex = false;
        printedObjects.forEach(function(obj, index){
            if(obj===value){
                printedObjIndex = index;
            }
        });

        if(printedObjIndex && typeof(value)=="object"){
            return "(see " + value.constructor.name.toLowerCase() + " 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);
}
csdnceshi68
local-host nice catch. yes obviously, always use hard equality and jshint to catch such silly mistakes.
大约 5 年之前 回复
weixin_41568127
?yb? Don't you mean ===? 0 == false is true, 0 === false is false. ;^) But I'd rather not initialize printedObjIndex to false, as then you can check against undefined so that you're (well, Trindaz's) not mixing metaphors as strangely.
大约 5 年之前 回复
csdnceshi68
local-host Nice code. You have a silly error though, you write if(printedObjIndex) while you should write if(printedObjIndex==false) because index can also be 0 which is translated to false unless you explicitly state otherwise.
7 年多之前 回复

Use the JSON.stringify method with a replacer. Read this documentation for more information. http://msdn.microsoft.com/en-us/library/cc836459%28v=vs.94%29.aspx

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

var replacement = {"b":undefined};

alert(JSON.stringify(obj,replacement));

Figure out a way to populate the replacement array with cyclic references. You can use the typeof method to find if an the property is of type 'object' ( reference ) and an exact equality check ( === ) to verify circular reference.

csdnceshi62
csdnceshi62 whoops, thanks for that, of course you're right
8 年多之前 回复
csdnceshi57
perhaps? This might only work in IE (considering the fact that MSDN is documentation from Microsoft, and Microsoft creates IE). In Firefox/Chrome, jsfiddle.net/ppmaW generates the circular reference error. FYI: var obj = {foo:obj} does not create a circular reference. Instead, it creates an object whose foo attribute refers to the previous value of obj (undefined if not previously defined, declared because of var obj).
8 年多之前 回复
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;
}
共19条数据 1 尾页
立即提问