Erro no sendrequest do Chrome: TypeError: Convertendo estrutura circular em JSON

384

Eu tenho o seguinte ...

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

que chama o seguinte ..

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

No entanto, meu código nunca atinge "ZOMG HERE", mas gera o seguinte erro ao executar chrome.extension.sendRequest

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

Alguém tem alguma idéia do que está causando isso?

Skizit
fonte
2
Você está tentando enviar um objeto que possui referências circulares. O que é pagedoc?
Felix Kling
9
O que quero dizer com o que? 1. Qual é o valor de pagedoc? 2. Referência circular:a = {}; a.b = a;
Felix Kling 27/01
11
Ahh .. isso consertou! Se você quiser responder isso, eu lhe darei crédito por isso!
Skizit
5
tente usar node.js: util.inspect
boldnik

Respostas:

489

Isso significa que o objeto que você passa na solicitação (eu acho que é pagedoc) tem uma referência circular, algo como:

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

JSON.stringify não pode converter estruturas como esta.

NB : Este seria o caso dos nós DOM, que possuem referências circulares, mesmo que não estejam anexados à árvore DOM. Cada nó tem um ownerDocumentque se refere documentna maioria dos casos. documenttem uma referência à árvore DOM pelo menos através document.bodye document.body.ownerDocumentse refere documentnovamente, que é apenas uma das várias referências circulares na árvore DOM.

Felix Kling
fonte
2
Obrigado! Isso explica o problema que recebi. Mas como a referência circular presente nos objetos DOM não causa problemas? O JSON restringiria um documentobjeto?
ASGs
3
@asgs: Ele faz causar problemas, pelo menos no Chrome. O Firefox parece ser um pouco mais inteligente, mas não sei exatamente o que está fazendo.
precisa
É possível "pegar" esse erro e resolvê-lo?
Doug Molineux
2
@DougMolineux: Claro, você pode usar try...catchpara capturar esse erro.
Felix Kling
4
@FelixKling Infelizmente eu não poderia chegar a esse trabalho (pode ter feito algo errado) Acabei usando este: github.com/isaacs/json-stringify-safe
Doug Molineux
128

De acordo com os documentos JSON no Mozilla , JSON.Stringifytem um segundo parâmetro censorque pode ser usado para filtrar / ignorar itens filhos enquanto analisa a árvore. No entanto, talvez você possa evitar as referências circulares.

No Node.js não podemos. Para que possamos fazer algo assim:

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

O resultado:

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

Infelizmente, parece haver um máximo de 30 iterações antes de assumir automaticamente que é circular. Caso contrário, isso deve funcionar. Eu até usei a areEquivalent partir daqui , mas JSON.Stringifyainda gera a exceção após 30 iterações. Ainda assim, é bom o suficiente para obter uma representação decente do objeto em um nível superior, se você realmente precisar. Talvez alguém possa melhorar isso? No Node.js para um objeto de solicitação HTTP, estou recebendo:

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

Criei um pequeno módulo Node.js para fazer isso aqui: https://github.com/ericmuyser/stringy Sinta-se à vontade para melhorar / contribuir!

Eric Muyser
fonte
10
É a primeira vez que vejo uma função sendo aprovada que retorna uma função auto-executável que retorna uma função regular. Acredito que entendi por que isso foi feito, mas não acredito que eu mesma teria encontrado essa solução e sinto que poderia me lembrar melhor dessa técnica se pudesse ver outros exemplos em que essa configuração é necessária. Dito isto, você poderia apontar alguma literatura relacionada a essa configuração / técnica (por falta de uma palavra melhor) ou similar?
Shawn
11
+1 para Shawn. Remova esse IEFE, é absolutamente inútil e ilegível.
#
11
thx por apontar o censor arg! permite a depuração de problemas circulares. no meu caso, eu tinha uma matriz de jquery onde gostaria de ter uma matriz normal. ambos parecem semelhantes no modo de impressão de depuração. Sobre o IEFE, eu os vejo frequentemente usados ​​em lugares onde não há absolutamente nenhuma necessidade deles e concordo com Shawn e Bergi que esse é exatamente o caso.
Citykid
11
Não sei por que, mas essa solução parece não funcionar para mim.
Nikola Schou
11
@BrunoLM: para um limite de 30 iterações, se você retornar '[Unknown:' + typeof(value) + ']', verá como corrigir o censor para tratar adequadamente as funções e alguns outros tipos.
Alex Pakka
46

Uma abordagem é retirar objetos e funções do objeto principal. E stringify a forma mais simples

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
fonte
2
Resposta perfeita para mim. Talvez a palavra-chave 'function' esteja perdida?
Stepan Loginov
28

Normalmente, uso o pacote circular-json npm para resolver isso.

// 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: circular-json foi descontinuado, agora uso flatted (do criador do 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"}]

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

user3139574
fonte
8

Com base na resposta do zainengineer ... Outra abordagem é fazer uma cópia profunda do objeto e remover referências circulares e restringir o resultado.

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
fonte
4

Eu resolvo esse problema no NodeJS assim:

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
fonte
2

Eu experimentei o mesmo erro ao tentar criar a mensagem abaixo com o jQuery. A referência circular acontece quando reviewerNameestava sendo atribuída por engano msg.detail.reviewerName. O .val () do JQuery corrigiu o problema, veja a última linha.

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
fonte
1

Eu estava recebendo o mesmo erro com o jQuery formvaliadator, mas quando removi um console.log dentro de success: function, ele funcionou.

Azmeer
fonte
0

No meu caso, eu estava recebendo esse erro quando estava usando a asyncfunção no lado do servidor para buscar documentos usando o mangusto. Acabou que a razão pela qual eu esqueci de colocar awaitantes de chamar o find({})método. A adição dessa parte corrigiu meu problema.

Mussa Charles
fonte
0

Isso funciona e informa quais propriedades são circulares. Também permite reconstruir o objeto com as referências

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

Exemplo com muito ruído removido:

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

Para reconstruir a chamada JSON.parse (), faça um loop pelas propriedades procurando a [Circular Reference]tag. Depois pique e ... avaliá-lo comthis o objeto raiz.

Não avalie nada que possa ser hackeado. A melhor prática seria string.split('.')procurar as propriedades pelo nome para definir a referência.

Derek Ziemba
fonte