Помилка при запиті Chrome: TypeError: Перетворення кругової структури в JSON


383

У мене таке:

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

Хтось має уявлення, що це викликає?


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 не може конвертувати такі структури.

Примітка . Це було б у випадку з вузлами DOM, які мають кругові посилання, навіть якщо вони не прикріплені до дерева DOM. Кожен вузол має значення, на ownerDocumentяке посилається documentв більшості випадків. documentмає посилання на дерево DOM щонайменше наскрізь document.bodyі document.body.ownerDocumentпосилається знову на це document, що є лише одним із безлічі круглих посилань у дереві DOM.


2
Дякую! Це пояснює проблему, яку я отримав. Але як кругова довідка, присутня в об'єктах DOM, не викликає жодних проблем? Чи впорядкував би JSON documentоб'єкт?
асгс

3
@asgs: це викликає проблеми, принаймні в Chrome. Firefox здається трохи розумнішим щодо цього, але я не знаю точно, що він робить.
Фелікс Клінг

Чи можна «зловити» цю помилку і впоратися з нею?
Doug Molineux

2
@DougMolineux: Звичайно, ви можете використовувати try...catchцю помилку.
Фелікс Клінг

4
@FelixKling На жаль, я не міг змусити це працювати (можливо, щось робив не так). Я закінчився цим: github.com/isaacs/json-stringify-safe
Doug

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
THX для вказівки аргументу цензора! це дозволяє налагоджувати кругові проблеми. у моєму випадку у мене був масив jquery, де я повинен мати нормальний масив. обидва вони схожі в режимі друку налагодження. Щодо IEFE, я бачу, що їх часто використовують у місцях, де в них абсолютно немає потреби, і погоджуюся з Шоном та Бергі, що це саме такий випадок.
citykid

1
Я не впевнений, чому, але, здається, це рішення не працює для мене.
Nikola Schou

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

2
Ідеальна відповідь для мене. Можливо, ключове слово "функція" пропущено?
Степан Логінов

28

Для вирішення цього питання я зазвичай використовую пакетний цикл json npm.

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

Примітка: circular-json застарілий, я зараз використовую сплющений (від творця 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


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

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

1

Я отримував таку ж помилку з jQuery formvaliadadator, але коли я видалив console.log всередині успіху: функція, вона працювала.


0

У моєму випадку я отримував цю помилку, коли використовував asyncфункцію на моєму сервері для отримання документів за допомогою мангуста. Виявилося, що причиною було те, що я забув поставити 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]тег. Потім відріжте це і ... eval ... це з thisвстановленим на кореневий об'єкт.

Не оцінюйте нічого, що можна зламати. Кращою практикою було б робити string.split('.')тоді пошук властивостей по імені, щоб встановити посилання.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.