Erreur de demande de Chrome: TypeError: Conversion d'une structure circulaire en JSON

384

J'ai ce qui suit ...

chrome.extension.sendRequest({
  req: "getDocument",
  docu: pagedoc,
  name: 'name'
}, function(response){
  var efjs = response.reply;
});

qui appelle ce qui suit ..

case "getBrowserForDocumentAttribute":
  alert("ZOMG HERE");
  sendResponse({
    reply: getBrowserForDocumentAttribute(request.docu,request.name)
  });
  break;

Cependant, mon code n'atteint jamais "ZOMG ICI" mais génère plutôt l'erreur suivante lors de l'exécution chrome.extension.sendRequest

 Uncaught TypeError: Converting circular structure to JSON
 chromeHidden.JSON.stringify
 chrome.Port.postMessage
 chrome.initExtension.chrome.extension.sendRequest
 suggestQuery

Quelqu'un at-il une idée de ce qui cause cela?

Skizit
la source
2
Vous essayez d'envoyer un objet contenant des références circulaires. Qu'est-ce que c'est pagedoc?
Felix Kling
9
Qu'est-ce que je veux dire avec quoi? 1. Quelle est la valeur de pagedoc? 2. Référence circulaire:a = {}; a.b = a;
Felix Kling
1
Ahh .. ça l'a réparé! Si vous souhaitez mettre cela dans une réponse, je vous en rendrai hommage!
Skizit
5
essayez d'utiliser node.js: util.inspect
boldnik

Réponses:

489

Cela signifie que l'objet que vous passez dans la demande (je suppose que c'est le cas pagedoc) a une référence circulaire, quelque chose comme:

var a = {};
a.b = a;

JSON.stringify ne peut pas convertir des structures comme celle-ci.

NB : Ce serait le cas des nœuds DOM, qui ont des références circulaires, même s'ils ne sont pas attachés à l'arborescence DOM. Chaque nœud a un ownerDocumentqui fait référence documentdans la plupart des cas. documenta une référence à l'arbre DOM au moins à travers document.bodyet document.body.ownerDocumentrenvoie à documentnouveau, ce qui est seulement une des multiples références circulaires dans l'arbre DOM.

Felix Kling
la source
2
Merci! Cela explique le problème que j'ai eu. Mais comment la référence circulaire présente dans les objets DOM ne pose-t-elle aucun problème? Est-ce que JSON stringifierait un documentobjet?
asgs
3
@asgs: Il fait causer des problèmes, au moins dans Chrome. Firefox semble être un peu plus intelligent à ce sujet, mais je ne sais pas exactement ce qu'il fait.
Felix Kling
Est-il possible de "capturer" cette erreur et de la gérer?
Doug Molineux
2
@DougMolineux: Bien sûr, vous pouvez utiliser try...catchpour attraper cette erreur.
Felix Kling
4
@FelixKling Malheureusement, je n'ai pas pu faire fonctionner cela (peut-être que je faisais quelque chose de mal) J'ai fini par utiliser ceci: github.com/isaacs/json-stringify-safe
Doug Molineux
128

Selon les documents JSON de Mozilla , JSON.Stringifya un deuxième paramètre censorqui peut être utilisé pour filtrer / ignorer les éléments enfants lors de l'analyse de l'arborescence. Cependant, vous pouvez peut-être éviter les références circulaires.

Dans Node.js, nous ne pouvons pas. Nous pouvons donc faire quelque chose comme ceci:

function censor(censor) {
  var i = 0;

  return function(key, value) {
    if(i !== 0 && typeof(censor) === 'object' && typeof(value) == 'object' && censor == value) 
      return '[Circular]'; 

    if(i >= 29) // seems to be a harded maximum of 30 serialized objects?
      return '[Unknown]';

    ++i; // so we know we aren't using the original object anymore

    return value;  
  }
}

var b = {foo: {bar: null}};

b.foo.bar = b;

console.log("Censoring: ", b);

console.log("Result: ", JSON.stringify(b, censor(b)));

Le résultat:

Censoring:  { foo: { bar: [Circular] } }
Result: {"foo":{"bar":"[Circular]"}}

Malheureusement, il semble y avoir un maximum de 30 itérations avant de supposer automatiquement qu'il est circulaire. Sinon, cela devrait fonctionner. J'ai même utilisé areEquivalent d'ici , mais JSON.Stringifyjette toujours l'exception après 30 itérations. Pourtant, c'est assez bon pour obtenir une représentation décente de l'objet à un niveau supérieur, si vous en avez vraiment besoin. Peut-être que quelqu'un peut améliorer cela? Dans Node.js pour un objet de requête HTTP, j'obtiens:

{
"limit": null,
"size": 0,
"chunks": [],
"writable": true,
"readable": false,
"_events": {
    "pipe": [null, null],
    "error": [null]
},
"before": [null],
"after": [],
"response": {
    "output": [],
    "outputEncodings": [],
    "writable": true,
    "_last": false,
    "chunkedEncoding": false,
    "shouldKeepAlive": true,
    "useChunkedEncodingByDefault": true,
    "_hasBody": true,
    "_trailer": "",
    "finished": false,
    "socket": {
        "_handle": {
            "writeQueueSize": 0,
            "socket": "[Unknown]",
            "onread": "[Unknown]"
        },
        "_pendingWriteReqs": "[Unknown]",
        "_flags": "[Unknown]",
        "_connectQueueSize": "[Unknown]",
        "destroyed": "[Unknown]",
        "bytesRead": "[Unknown]",
        "bytesWritten": "[Unknown]",
        "allowHalfOpen": "[Unknown]",
        "writable": "[Unknown]",
        "readable": "[Unknown]",
        "server": "[Unknown]",
        "ondrain": "[Unknown]",
        "_idleTimeout": "[Unknown]",
        "_idleNext": "[Unknown]",
        "_idlePrev": "[Unknown]",
        "_idleStart": "[Unknown]",
        "_events": "[Unknown]",
        "ondata": "[Unknown]",
        "onend": "[Unknown]",
        "_httpMessage": "[Unknown]"
    },
    "connection": "[Unknown]",
    "_events": "[Unknown]",
    "_headers": "[Unknown]",
    "_headerNames": "[Unknown]",
    "_pipeCount": "[Unknown]"
},
"headers": "[Unknown]",
"target": "[Unknown]",
"_pipeCount": "[Unknown]",
"method": "[Unknown]",
"url": "[Unknown]",
"query": "[Unknown]",
"ended": "[Unknown]"
}

J'ai créé un petit module Node.js pour le faire ici: https://github.com/ericmuyser/stringy N'hésitez pas à améliorer / contribuer!

Eric Muyser
la source
10
C'est la première fois que je vois passer une fonction qui renvoie une fonction auto-exécutable qui renvoie une fonction régulière. Je crois que je comprends pourquoi cela a été fait, mais je ne pense pas que j'aurais trouvé cette solution moi-même, et je pense que je me souviendrais mieux de cette technique si je pouvais voir d'autres exemples où cette configuration est nécessaire. Cela étant dit, pourriez-vous indiquer de la littérature concernant cette configuration / technique (faute d'un meilleur mot) ou similaire?
Shawn
1
+1 à Shawn. Veuillez supprimer cet IEFE, il est absolument inutile et illisible.
Bergi
1
merci d'avoir signalé l'argument de la censure! il permet de déboguer les problèmes circulaires. dans mon cas, j'avais un tableau jquery où je pensais avoir un tableau normal. ils se ressemblent tous les deux en mode d'impression de débogage. À propos de l'IEFE, je les vois fréquemment utilisés dans des endroits où ils ne sont absolument pas nécessaires et je suis d'accord avec Shawn et Bergi que c'est exactement le cas.
citykid
1
Je ne sais pas pourquoi, mais cette solution ne semble pas fonctionner pour moi.
Nikola Schou
1
@BrunoLM: pour une limite de 30 itérations, si vous revenez, '[Unknown:' + typeof(value) + ']'vous verrez comment corriger la censure pour traiter correctement les fonctions et certains autres types.
Alex Pakka
46

Une approche consiste à retirer l'objet et les fonctions de l'objet principal. Et stringify la forme plus simple

function simpleStringify (object){
    var simpleObject = {};
    for (var prop in object ){
        if (!object.hasOwnProperty(prop)){
            continue;
        }
        if (typeof(object[prop]) == 'object'){
            continue;
        }
        if (typeof(object[prop]) == 'function'){
            continue;
        }
        simpleObject[prop] = object[prop];
    }
    return JSON.stringify(simpleObject); // returns cleaned up JSON
};
zainengineer
la source
2
Réponse parfaite pour moi. Peut-être que le mot clé «fonction» a été manqué?
Stepan Loginov
28

J'utilise normalement le paquetage circulaire-json npm pour résoudre ce problème.

// Felix Kling's example
var a = {};
a.b = a;
// load circular-json module
var CircularJSON = require('circular-json');
console.log(CircularJSON.stringify(a));
//result
{"b":"~"}

Remarque: circular-json est obsolète, j'utilise maintenant flat (du créateur de CircularJSON):

// ESM
import {parse, stringify} from 'flatted/esm';

// CJS
const {parse, stringify} = require('flatted/cjs');

const a = [{}];
a[0].a = a;
a.push(a);

stringify(a); // [["1","0"],{"a":"0"}]

sur: https://www.npmjs.com/package/flatted

user3139574
la source
8

Basé sur la réponse de zainengineer ... Une autre approche consiste à faire une copie complète de l'objet et à supprimer les références circulaires et à filtrer le résultat.

function cleanStringify(object) {
    if (object && typeof object === 'object') {
        object = copyWithoutCircularReferences([object], object);
    }
    return JSON.stringify(object);

    function copyWithoutCircularReferences(references, object) {
        var cleanObject = {};
        Object.keys(object).forEach(function(key) {
            var value = object[key];
            if (value && typeof value === 'object') {
                if (references.indexOf(value) < 0) {
                    references.push(value);
                    cleanObject[key] = copyWithoutCircularReferences(references, value);
                    references.pop();
                } else {
                    cleanObject[key] = '###_Circular_###';
                }
            } else if (typeof value !== 'function') {
                cleanObject[key] = value;
            }
        });
        return cleanObject;
    }
}

// Example

var a = {
    name: "a"
};

var b = {
    name: "b"
};

b.a = a;
a.b = b;

console.log(cleanStringify(a));
console.log(cleanStringify(b));

CM
la source
4

Je résous ce problème sur NodeJS comme ceci:

var util = require('util');

// Our circular object
var obj = {foo: {bar: null}, a:{a:{a:{a:{a:{a:{a:{hi: 'Yo!'}}}}}}}};
obj.foo.bar = obj;

// Generate almost valid JS object definition code (typeof string)
var str = util.inspect(b, {depth: null});

// Fix code to the valid state (in this example it is not required, but my object was huge and complex, and I needed this for my case)
str = str
    .replace(/<Buffer[ \w\.]+>/ig, '"buffer"')
    .replace(/\[Function]/ig, 'function(){}')
    .replace(/\[Circular]/ig, '"Circular"')
    .replace(/\{ \[Function: ([\w]+)]/ig, '{ $1: function $1 () {},')
    .replace(/\[Function: ([\w]+)]/ig, 'function $1(){}')
    .replace(/(\w+): ([\w :]+GMT\+[\w \(\)]+),/ig, '$1: new Date("$2"),')
    .replace(/(\S+): ,/ig, '$1: null,');

// Create function to eval stringifyed code
var foo = new Function('return ' + str + ';');

// And have fun
console.log(JSON.stringify(foo(), null, 4));
MiF
la source
2

J'ai rencontré la même erreur lors de la création du message ci-dessous avec jQuery. La référence circulaire se produit lors d' reviewerNameune affectation par erreur à msg.detail.reviewerName. .Val () de JQuery a résolu le problème, voir dernière ligne.

var reviewerName = $('reviewerName'); // <input type="text" id="taskName" />;
var msg = {"type":"A", "detail":{"managerReview":true} };
msg.detail.reviewerName = reviewerName; // Error
msg.detail.reviewerName = reviewerName.val(); // Fixed
izilotti
la source
1

J'obtenais la même erreur avec jQuery formvaliadator, mais quand j'ai supprimé un console.log dans success: function, cela a fonctionné.

Azmeer
la source
0

Pour mon cas, j'obtenais cette erreur lorsque j'utilisais la asyncfonction côté serveur pour récupérer des documents à l'aide de mangouste. Il s'est avéré que la raison était que j'avais oublié de mettre awaitavant d'appeler la find({})méthode. L'ajout de cette partie a résolu mon problème.

Mussa Charles
la source
0

Cela fonctionne et vous indique quelles propriétés sont circulaires. Il permet également de reconstruire l'objet avec les références

  JSON.stringifyWithCircularRefs = (function() {
    const refs = new Map();
    const parents = [];
    const path = ["this"];

    function clear() {
      refs.clear();
      parents.length = 0;
      path.length = 1;
    }

    function updateParents(key, value) {
      var idx = parents.length - 1;
      var prev = parents[idx];
      if (prev[key] === value || idx === 0) {
        path.push(key);
        parents.push(value);
      } else {
        while (idx-- >= 0) {
          prev = parents[idx];
          if (prev[key] === value) {
            idx += 2;
            parents.length = idx;
            path.length = idx;
            --idx;
            parents[idx] = value;
            path[idx] = key;
            break;
          }
        }
      }
    }

    function checkCircular(key, value) {
      if (value != null) {
        if (typeof value === "object") {
          if (key) { updateParents(key, value); }

          let other = refs.get(value);
          if (other) {
            return '[Circular Reference]' + other;
          } else {
            refs.set(value, path.join('.'));
          }
        }
      }
      return value;
    }

    return function stringifyWithCircularRefs(obj, space) {
      try {
        parents.push(obj);
        return JSON.stringify(obj, checkCircular, space);
      } finally {
        clear();
      }
    }
  })();

Exemple avec beaucoup de bruit supprimé:

{
    "requestStartTime": "2020-05-22...",
    "ws": {
        "_events": {},
        "readyState": 2,
        "_closeTimer": {
            "_idleTimeout": 30000,
            "_idlePrev": {
                "_idleNext": "[Circular Reference]this.ws._closeTimer",
                "_idlePrev": "[Circular Reference]this.ws._closeTimer",
                "expiry": 33764,
                "id": -9007199254740987,
                "msecs": 30000,
                "priorityQueuePosition": 2
            },
            "_idleNext": "[Circular Reference]this.ws._closeTimer._idlePrev",
            "_idleStart": 3764,
            "_destroyed": false
        },
        "_closeCode": 1006,
        "_extensions": {},
        "_receiver": {
            "_binaryType": "nodebuffer",
            "_extensions": "[Circular Reference]this.ws._extensions",
        },
        "_sender": {
            "_extensions": "[Circular Reference]this.ws._extensions",
            "_socket": {
                "_tlsOptions": {
                    "pipe": false,
                    "secureContext": {
                        "context": {},
                        "singleUse": true
                    },
                },
                "ssl": {
                    "_parent": {
                        "reading": true
                    },
                    "_secureContext": "[Circular Reference]this.ws._sender._socket._tlsOptions.secureContext",
                    "reading": true
                }
            },
            "_firstFragment": true,
            "_compress": false,
            "_bufferedBytes": 0,
            "_deflating": false,
            "_queue": []
        },
        "_socket": "[Circular Reference]this.ws._sender._socket"
    }
}

Pour reconstruire, appelez JSON.parse (), puis parcourez les propriétés à la recherche de la [Circular Reference]balise. Ensuite, coupez cela et ... évaluez ... avecthis ensemble sur l'objet racine.

N'évaluez rien qui puisse être piraté. Une meilleure pratique serait de string.split('.')rechercher ensuite les propriétés par nom pour définir la référence.

Derek Ziemba
la source