Błąd żądania wysłania Chrome: TypeError: Konwertowanie okrągłej struktury na JSON

382

Mam następujące ...

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

który wywołuje następujące ...

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

Jednak mój kod nigdy nie osiąga „ZOMG TUTAJ”, ale generuje następujący błąd podczas działania chrome.extension.sendRequest

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

Czy ktoś ma pojęcie, co to powoduje?

Skizit
źródło
2
Próbujesz wysłać obiekt zawierający odwołania cykliczne. Co to jest pagedoc?
Felix Kling
9
Co mam na myśli przez co? 1. Jaka jest wartość pagedoc? 2. Odnośnik okólny:a = {}; a.b = a;
Felix Kling
1
Ahh .. to naprawiło! Jeśli chcesz odpowiedzieć na to pytanie, dam ci za to uznanie!
Skizit 27.01.11
5
spróbuj użyć node.js: util.inspect
boldnik

Odpowiedzi:

488

Oznacza to, że obiekt przekazywany w żądaniu (tak mi się wydaje pagedoc) ma okrągłe odniesienie, coś w stylu:

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

JSON.stringify nie może konwertować takich struktur.

Uwaga : tak byłoby w przypadku węzłów DOM, które mają odwołania cykliczne, nawet jeśli nie są one dołączone do drzewa DOM. Każdy węzeł ma taki, ownerDocumentktóry documentw większości przypadków odnosi się do . documentzawiera odniesienie do drzewa DOM przynajmniej przez document.bodyi document.body.ownerDocumentodwołuje się documentponownie, który jest tylko jednym z wielu okrągłych odniesień w drzewie DOM.

Felix Kling
źródło
2
Dzięki! To wyjaśnia problem, który mam. Ale w jaki sposób odwołanie cykliczne obecne w obiektach DOM nie powoduje żadnych problemów? Czy JSON utworzy documentobiekt?
asgs
3
@asgs: To robi przyczyną problemów, przynajmniej w Chrome. Firefox wydaje się nieco mądrzejszy, ale nie wiem dokładnie, co robi.
Felix Kling
Czy można „złapać” ten błąd i go obsłużyć?
Doug Molineux,
2
@DougMolineux: Jasne, możesz użyć, try...catchaby złapać ten błąd.
Felix Kling
4
@FelixKling Niestety nie udało mi się tego zrobić (mogłem zrobić coś źle) Skończyło się na tym: github.com/isaacs/json-stringify-safe
Doug Molineux
128

Zgodnie z docs JSON w Mozilli , JSON.Stringifyposiada drugi parametr censor, który może być używany do filtra / ignorować dzieci przedmiotów podczas parsowania drzewa. Być może jednak można uniknąć okrągłych odniesień.

W Node.js nie możemy. Możemy więc zrobić coś takiego:

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

Wynik:

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

Niestety wydaje się, że może być maksymalnie 30 iteracji, zanim automatycznie przyjmie, że jest kołowy. W przeciwnym razie powinno to działać. Użyłem nawet areEquivalent stąd , ale JSON.Stringifynadal rzuca wyjątek po 30 iteracjach. Nadal wystarczy uzyskać przyzwoitą reprezentację obiektu na najwyższym poziomie, jeśli naprawdę go potrzebujesz. Być może jednak ktoś może to poprawić? W Node.js dla obiektu żądania HTTP otrzymuję:

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

Stworzyłem mały moduł Node.js, aby to zrobić tutaj: https://github.com/ericmuyser/stringy Nie krępuj się ulepszać / przyczyniać się!

Eric Muyser
źródło
10
Po raz pierwszy widzę przekazywaną funkcję, która zwraca funkcję samoczynnego działania, która zwraca funkcję zwykłą. Wydaje mi się, że rozumiem, dlaczego tak się stało, ale nie sądzę, żebym sam znalazł to rozwiązanie i czuję, że lepiej zapamiętałbym tę technikę, gdybym mógł zobaczyć inne przykłady, w których taka konfiguracja jest potrzebna. Biorąc to pod uwagę, czy mógłbyś wskazać literaturę dotyczącą tej konfiguracji / techniki (z powodu braku lepszego słowa) lub podobnych?
Shawn
1
+1 do Shawna. Usuń ten IEFE, jest absolutnie bezużyteczny i nieczytelny.
Bergi
1
dzięki za wskazanie arg cenzora! pozwala debugować problemy okrągłe. w moim przypadku miałem tablicę jquery, w której powinienem mieć normalną tablicę. oba wyglądają podobnie w trybie drukowania debugowania. Jeśli chodzi o IEFE, widzę je często używane w miejscach, w których absolutnie ich nie ma potrzeby, i zgadzam się z Shawnem i Bergim, że tak właśnie jest.
citykid
1
Nie jestem pewien dlaczego, ale wydaje mi się, że to rozwiązanie nie działa.
Nikola Schou,
1
@BrunoLM: dla limitu 30 iteracji, jeśli wrócisz '[Unknown:' + typeof(value) + ']', zobaczysz, jak naprawić cenzurę, aby odpowiednio traktować funkcje i niektóre inne typy.
Alex Pakka
46

Jednym z podejść jest usunięcie obiektu i funkcji z głównego obiektu. I sprecyzuj prostszą formę

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
źródło
2
Idealna odpowiedź dla mnie. Może pominięto słowo kluczowe „funkcja”?
Stepan Loginov
28

Zwykle używam pakietu npm circular-json, aby rozwiązać ten problem.

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

Uwaga: okrągły-json został wycofany, teraz używam spłaszczonego (od twórcy 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"}]

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

użytkownik3139574
źródło
8

Oparty na odpowiedzi zainengineera ... Innym podejściem jest wykonanie głębokiej kopii obiektu i usunięcie okrągłych odniesień oraz zszeregowanie wyniku.

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
źródło
4

Rozwiązuję ten problem w NodeJS w następujący sposób:

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
źródło
2

Wystąpił ten sam błąd podczas próby zbudowania poniższego komunikatu za pomocą jQuery. Okrągłe odniesienie ma miejsce, gdy reviewerNamezostał przypadkowo przypisany msg.detail.reviewerName. .Val () w JQuery naprawił problem, patrz ostatni wiersz.

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
źródło
1

Otrzymałem ten sam błąd z formvaliadatorem jQuery, ale kiedy usunąłem console.log wewnątrz sukcesu: funkcja, zadziałało.

Azmeer
źródło
0

W moim przypadku ten błąd pojawiał się, gdy korzystałem z asyncfunkcji po stronie serwera, aby pobierać dokumenty za pomocą mongoose. Okazało się, że powodem było to, że zapomniałem podać awaitprzed wywołaniem find({})metody. Dodanie tej części rozwiązało mój problem.

Mussa Charles
źródło
0

To działa i mówi, które właściwości są okrągłe. Pozwala także na rekonstrukcję obiektu z referencjami

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

Przykład z dużą ilością usuniętego hałasu:

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

Aby zrekonstruować, wywołaj JSON.parse (), a następnie przejdź przez właściwości szukające [Circular Reference]znacznika. Następnie odetnij to i ... ewaluuj ... za pomocąthis ustawiając obiekt główny.

Nie sprawdzaj niczego, co można zhakować. Lepszą praktyką byłoby string.split('.')sprawdzenie właściwości według nazwy w celu ustawienia odniesienia.

Derek Ziemba
źródło