Chrome sendrequest error: TypeError: Преобразование круговой структуры в JSON

384

У меня есть следующее ...

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

который вызывает следующее ..

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

Тем не менее, мой код никогда не достигает "ZOMG ЗДЕСЬ", а скорее выдает следующую ошибку при запуске chrome.extension.sendRequest

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

У кого-нибудь есть идеи, что вызывает это?

Skizit
источник
2
Вы пытаетесь отправить объект с круговыми ссылками. Что такое pagedoc?
Феликс Клинг
9
Что я имею в виду с чем? 1. Какова стоимость pagedoc? 2. Циркулярная ссылка:a = {}; a.b = a;
Феликс Клинг
1
Ааа .. это исправило это! Если вы хотите добавить это в ответ, я воздаю вам должное!
Skizit
5
попробуйте использовать node.js: util.inspect
boldnik

Ответы:

489

Это означает, что объект, который вы передаете в запросе (я предполагаю, что это так pagedoc), имеет циклическую ссылку, что-то вроде:

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

JSON.stringify не может конвертировать такие структуры

NB : Это было бы в случае с узлами DOM, которые имеют циклические ссылки, даже если они не привязаны к дереву DOM. Каждый узел имеет ссылку, на ownerDocumentкоторую ссылается documentв большинстве случаев. documentимеет ссылку на дерево DOM, по крайней мере, через document.bodyи document.body.ownerDocumentссылается documentснова, что является лишь одной из нескольких циклических ссылок в дереве DOM.

Феликс Клинг
источник
2
Спасибо! Это объясняет проблему, которую я получил. Но как круговая ссылка, присутствующая в объектах DOM, не вызывает никаких проблем? Будет ли JSON структурировать documentобъект?
просит
3
@asgs: это вызывает проблемы, по крайней мере, в Chrome. Firefox кажется немного умнее, но я не знаю точно, что он делает.
Феликс Клинг
Можно ли «поймать» эту ошибку и обработать ее?
Дуг Молинью
2
@DougMolineux: Конечно, вы можете использовать, try...catchчтобы поймать эту ошибку.
Феликс Клинг
4
@FelixKling К сожалению, я не мог заставить это работать (возможно, делал что-то не так), я закончил тем, что использовал это: github.com/isaacs/json-stringify-safe
Дуг Молинью
128

Согласно документам JSON в Mozilla , JSON.Stringifyесть второй параметр, censorкоторый можно использовать для фильтрации / игнорирования дочерних элементов при анализе дерева. Однако, возможно, вы можете избежать циклических ссылок.

В Node.js мы не можем. Таким образом, мы можем сделать что-то вроде этого:

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

Результат:

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

К сожалению, кажется, что максимум 30 итераций, прежде чем он автоматически примет круговую форму. В противном случае это должно работать. Я даже использовал areEquivalent отсюда , но JSON.Stringifyвсе равно выдает исключение после 30 итераций. Тем не менее, достаточно хорошо получить достойное представление об объекте на верхнем уровне, если он вам действительно нужен. Возможно, кто-то может улучшить это хотя? В Node.js для объекта HTTP-запроса я получаю:

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

Я создал небольшой модуль Node.js для этого здесь: https://github.com/ericmuyser/stringy Не стесняйтесь улучшать / вносить свой вклад!

Эрик Муйсер
источник
10
Это первый раз, когда я вижу, как передается функция, которая возвращает самовыполняющуюся функцию, которая возвращает обычную функцию. Я верю, что понимаю, почему это было сделано, но я не верю, что нашел бы это решение сам, и я чувствую, что мог бы лучше запомнить эту технику, если бы я мог видеть другие примеры, где эта установка необходима. При этом, не могли бы вы указать какую-либо литературу, касающуюся этой установки / техники (из-за отсутствия лучшего слова) или подобных?
Шон
1
+1 к Шону. Пожалуйста, удалите этот IEFE, он абсолютно бесполезен и неразборчив.
Берги
1
спасибо за указание на цензор arg! это позволяет отлаживать циклические проблемы. в моем случае у меня был массив jquery, где я мог иметь нормальный массив. они оба выглядят одинаково в режиме отладочной печати. Что касается IEFE, я вижу их часто используемыми в местах, где они абсолютно не нужны, и согласен с Шоном и Берги в том, что это как раз такой случай.
citykid
1
Я не уверен почему, но это решение, похоже, не работает для меня.
Никола Шоу
1
@BrunoLM: для ограничения 30 итераций, если вы вернетесь, '[Unknown:' + typeof(value) + ']'вы увидите, как исправить цензор, чтобы правильно обрабатывать функции и некоторые другие типы.
Алекс Пакка
46

Один из подходов состоит в том, чтобы отделить объект и функции от основного объекта. И зачеркните более простую форму

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
источник
2
Идеальный ответ для меня. Может быть, ключевое слово 'function' пропущено?
Степан Логинов
28

Я обычно использую пакет Round-JSON для решения этой проблемы.

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

Примечание: циркуляр-json устарел, теперь я использую flatted (от создателя 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"}]

от: https://www.npmjs.com/package/flatted

user3139574
источник
8

Основано на ответе zainengineer ... Другой подход заключается в создании глубокой копии объекта, отбрасывании круговых ссылок и строковом преобразовании результата.

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

Я решаю эту проблему на NodeJS следующим образом:

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
источник
2

Я столкнулся с той же ошибкой при попытке построить сообщение ниже с помощью jQuery. Циркулярное указание происходит, когда reviewerNameего ошибочно назначают msg.detail.reviewerName. .Val () JQuery исправил проблему, смотрите последнюю строку.

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
источник
1

Я получал ту же ошибку с jQuery formvaliadator, но когда я удалил console.log внутри success: function, это сработало.

Azmeer
источник
0

В моем случае я получал эту ошибку, когда использовал asyncфункцию на своем сервере для получения документов с помощью mongoose. Оказалось, что причина была в том, что я забыл поставить awaitперед вызовом find({})метода. Добавление этой части исправило мою проблему.

Мусса Чарльз
источник
0

Это работает и говорит вам, какие свойства являются круглыми. Это также позволяет реконструировать объект со ссылками

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

Пример с удалением большого количества шума:

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

Чтобы восстановить вызов JSON.parse (), затем переберите свойства, ища [Circular Reference]тег. Затем отрубите это и ... оцените этоthis установленным корневым объектом.

Не оценивайте ничего, что можно взломать. Лучше практиковать string.split('.')поиск свойств по имени, чтобы установить ссылку.

Дерек Зимба
источник