Errore sendrequest di Chrome: TypeError: conversione della struttura circolare in JSON


384

Ho il seguente ...

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

che chiama il seguente ..

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

Tuttavia, il mio codice non raggiunge mai "ZOMG QUI", ma genera il seguente errore durante l'esecuzione chrome.extension.sendRequest

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

Qualcuno ha idea di cosa sta causando questo?


2
Stai tentando di inviare un oggetto con riferimenti circolari al suo interno. Che cosa è pagedoc?
Felix Kling,

9
Cosa intendo con cosa? 1. Qual è il valore di pagedoc? 2. Riferimento circolare:a = {}; a.b = a;
Felix Kling

1
Ahh .. che risolto! Se vuoi inserire una risposta, ti darò credito!
Skizit,

5
prova a usare node.js: util.inspect
boldnik

Risposte:


489

Vuol dire che l'oggetto che passi nella richiesta (suppongo che lo sia pagedoc) ha un riferimento circolare, qualcosa del tipo:

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

JSON.stringify impossibile convertire strutture come questa.

NB : Questo sarebbe il caso dei nodi DOM, che hanno riferimenti circolari, anche se non sono collegati all'albero DOM. Ogni nodo ha un ownerDocumentche si riferisce documentnella maggior parte dei casi. documentha almeno un riferimento all'albero DOM document.bodye document.body.ownerDocumentfa nuovamente riferimento document, che è solo uno dei riferimenti circolari multipli nell'albero DOM.


2
Grazie! Questo spiega il problema che ho avuto. Ma in che modo il riferimento circolare presente negli oggetti DOM non causa alcun problema? JSON dovrebbe stringere un documentoggetto?
chiede il

3
@asgs: Si fa causa problemi, almeno in Chrome. Firefox sembra essere un po 'più intelligente, ma non so esattamente cosa stia facendo.
Felix Kling

È possibile "catturare" questo errore e gestirlo?
Doug Molineux,

2
@DougMolineux: certo, è possibile utilizzare try...catchper rilevare questo errore.
Felix Kling,

4
@FelixKling Purtroppo non sono riuscito a farlo funzionare (potrebbe aver fatto qualcosa di sbagliato) Ho finito per usare questo: github.com/isaacs/json-stringify-safe
Doug Molineux

128

Secondo i documenti JSON su Mozilla , JSON.Stringifyha un secondo parametrocensor che può essere utilizzato per filtrare / ignorare gli elementi figlio durante l'analisi dell'albero. Tuttavia, forse puoi evitare i riferimenti circolari.

In Node.js non possiamo. Quindi possiamo fare qualcosa del genere:

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

Il risultato:

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

Sfortunatamente sembra che ci siano un massimo di 30 iterazioni prima di assumere automaticamente che sia circolare. Altrimenti, dovrebbe funzionare. Ho anche usato areEquivalent da qui , ma JSON.Stringifygenera ancora l'eccezione dopo 30 iterazioni. Tuttavia, è abbastanza buono per ottenere una rappresentazione decente dell'oggetto ai massimi livelli, se ne hai davvero bisogno. Forse qualcuno può migliorare su questo però? In Node.js per un oggetto richiesta HTTP, sto ottenendo:

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

Ho creato un piccolo modulo Node.js per farlo qui: https://github.com/ericmuyser/stringy Sentiti libero di migliorare / contribuire!


10
È la prima volta che vedo passare una funzione che restituisce una funzione di auto-esecuzione che restituisce una funzione regolare. Credo di capire perché questo è stato fatto, ma non credo che avrei trovato quella soluzione da solo, e sento di poter ricordare meglio questa tecnica se potessi vedere altri esempi in cui è necessaria questa configurazione . Detto questo, potresti indicare qualche letteratura riguardante questa impostazione / tecnica (per mancanza di una parola migliore) o simili?
Shawn,

1
+1 a Shawn. Si prega di rimuovere quell'IEFE, è assolutamente inutile e illeggibile.
Bergi,

1
grazie per aver sottolineato la censura arg! consente il debug di problemi circolari. nel mio caso avevo un array jquery in cui avrei dovuto avere un array normale. entrambi sembrano simili nella modalità di stampa di debug. Riguardo all'IEFE, li vedo frequentemente usati in luoghi dove non ce n'è assolutamente bisogno e sono d'accordo con Shawn e Bergi sul fatto che sia proprio così.
citykid

1
Non sono sicuro del perché, ma questa soluzione non sembra funzionare per me.
Nikola Schou,

1
@BrunoLM: per un limite di 30 iterazioni, se ritorni '[Unknown:' + typeof(value) + ']'vedrai come correggere il censore per trattare correttamente le funzioni e alcuni altri tipi.
Alex Pakka,

46

Un approccio è quello di eliminare l'oggetto e le funzioni dall'oggetto principale. E stringi la forma più semplice

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

2
Risposta perfetta per me Forse la parola chiave "funzione" è mancata?
Stepan Loginov,

28

Normalmente uso il pacchetto npm circular-json per risolvere questo problema.

// 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":"~"}

Nota: circolare-json è stato deprecato, ora utilizzo flat (dal creatore di 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"}]

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


8

Basato sulla risposta di zainengineer ... Un altro approccio è quello di fare una copia profonda dell'oggetto e rimuovere i riferimenti circolari e stringere il risultato.

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



4

Risolvo questo problema su NodeJS in questo modo:

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

2

Ho riscontrato lo stesso errore durante il tentativo di compilare il messaggio seguente con jQuery. Il riferimento circolare si verifica quando reviewerNameè stato assegnato erroneamente a msg.detail.reviewerName. .Val () di JQuery ha risolto il problema, vedere l'ultima riga.

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

1

Stavo ottenendo lo stesso errore con jQuery formvaliadator, ma quando ho rimosso un console.log all'interno di successo: funzione, ha funzionato.


0

Nel mio caso stavo ottenendo quell'errore quando stavo usando la asyncfunzione sul mio server per recuperare documenti usando mongoose. Si è scoperto che il motivo era che ho dimenticato di mettere awaitprima di chiamare il find({})metodo. L'aggiunta di quella parte ha risolto il mio problema.


0

Funziona e ti dice quali proprietà sono circolari. Permette anche di ricostruire l'oggetto con i riferimenti

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

Esempio con molto rumore rimosso:

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

Per ricostruire chiamare JSON.parse () quindi scorrere le proprietà cercando il [Circular Reference]tag. Quindi tagliatelo via e ... valutatelo ... con thisset sull'oggetto root.

Non valutare nulla che possa essere violato. La migliore pratica sarebbe fare string.split('.')quindi cercare le proprietà per nome per impostare il riferimento.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.