Kesalahan kirim-pertanyaan Chrome: TypeError: Mengubah struktur lingkaran menjadi JSON

384

Saya punya yang berikut ...

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

yang memanggil berikut ini ..

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

Namun, kode saya tidak pernah mencapai "ZOMG DI SINI" tetapi lebih tepatnya melemparkan kesalahan berikut saat berjalan chrome.extension.sendRequest

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

Adakah yang tahu apa yang menyebabkan ini?

Skizit
sumber
2
Anda mencoba mengirim objek yang memiliki referensi melingkar di dalamnya. Apa pagedoc?
Felix Kling
9
Apa yang saya maksud dengan apa? 1. Apa nilai dari pagedoc? 2. Referensi melingkar:a = {}; a.b = a;
Felix Kling
1
Ahh .. itu sudah diperbaiki! Jika Anda ingin memasukkannya ke dalam jawaban, saya akan memberi Anda kredit untuk itu!
Skizit
5
coba gunakan node.js: util.inspect
boldnik

Jawaban:

489

Ini berarti bahwa objek yang Anda lewati dalam permintaan (saya rasa itu pagedoc) memiliki referensi melingkar, seperti:

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

JSON.stringify tidak dapat mengubah struktur seperti ini.

NB : Ini akan menjadi kasus dengan node DOM, yang memiliki referensi melingkar, bahkan jika mereka tidak melekat pada pohon DOM. Setiap node memiliki ownerDocumentyang mengacu documentpada sebagian besar kasus. documentmemiliki referensi ke pohon DOM setidaknya melalui document.bodydan document.body.ownerDocumentmerujuk kembali ke documentlagi, yang hanya merupakan salah satu dari beberapa referensi melingkar di pohon DOM.

Felix Kling
sumber
2
Terima kasih! Ini menjelaskan masalah yang saya dapatkan. Tetapi bagaimana referensi melingkar hadir dalam objek DOM tidak menyebabkan masalah? Apakah JSON akan mengencangkan documentobjek?
asgs
3
@gs: Ini memang menyebabkan masalah, setidaknya di Chrome. Firefox tampaknya sedikit lebih pintar tentang hal itu, tetapi saya tidak tahu persis apa yang dilakukannya.
Felix Kling
Apakah mungkin untuk "menangkap" kesalahan ini dan menanganinya?
Doug Molineux
2
@ DougMolineux: Tentu, Anda bisa menggunakan try...catchuntuk menangkap kesalahan ini.
Felix Kling
4
@FelixKling Sayangnya saya tidak bisa melakukan itu (mungkin telah melakukan sesuatu yang salah) Saya akhirnya menggunakan ini: github.com/isaacs/json-stringify-safe
Doug Molineux
128

Sesuai dokumen JSON di Mozilla , JSON.Stringifymemiliki parameter kedua censoryang dapat digunakan untuk memfilter / mengabaikan item anak-anak saat mengurai pohon. Namun, mungkin Anda dapat menghindari referensi melingkar.

Di Node.js kita tidak bisa. Jadi kita bisa melakukan sesuatu seperti ini:

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

Hasil:

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

Sayangnya sepertinya ada maksimum 30 iterasi sebelum secara otomatis mengasumsikan itu melingkar. Kalau tidak, ini akan berhasil. Saya bahkan menggunakan areEquivalent dari sini , tetapi JSON.Stringifymasih melempar pengecualian setelah 30 iterasi. Namun, itu cukup baik untuk mendapatkan representasi objek yang layak di tingkat atas, jika Anda benar-benar membutuhkannya. Mungkin seseorang dapat memperbaiki ini? Di Node.js untuk objek permintaan HTTP, saya mendapatkan:

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

Saya membuat modul Node.js kecil untuk melakukan ini di sini: https://github.com/ericmuyser/stringy Jangan ragu untuk meningkatkan / berkontribusi!

Eric Muyser
sumber
10
Ini adalah pertama kalinya saya melihat fungsi yang dikirimkan yang mengembalikan fungsi self-executing yang mengembalikan fungsi biasa. Saya percaya saya mengerti mengapa ini dilakukan, tetapi saya tidak percaya saya akan menemukan solusi itu sendiri, dan saya merasa saya bisa mengingat teknik ini dengan lebih baik jika saya bisa melihat contoh lain di mana pengaturan ini diperlukan. Yang sedang berkata, dapatkah Anda menunjukkan literatur tentang pengaturan / teknik ini (karena tidak ada kata yang lebih baik) atau yang serupa?
Shawn
1
+1 ke Shawn. Harap hapus IEFE itu, sama sekali tidak berguna dan tidak terbaca.
Bergi
1
Terima kasih untuk menunjukkan sensor arg! itu memungkinkan debugging ke masalah sirkular. dalam kasus saya, saya punya array jquery di mana saya harus memiliki array normal. keduanya terlihat serupa dalam mode cetak debug. Tentang IEFE, saya melihat mereka sering digunakan di tempat-tempat di mana sama sekali tidak perlu bagi mereka dan setuju dengan Shawn dan Bergi bahwa ini hanyalah kasus seperti itu.
citykid
1
Saya tidak yakin mengapa, tetapi solusi ini sepertinya tidak berhasil untuk saya.
Nikola Schou
1
@ BrunoLM: untuk 30 batas iterasi, jika Anda kembali, '[Unknown:' + typeof(value) + ']'Anda akan melihat cara memperbaiki sensor untuk memperlakukan fungsi dengan benar dan beberapa jenis lainnya.
Alex Pakka
46

Salah satu pendekatan adalah strip objek dan fungsi dari objek utama. Dan meringkas bentuk yang lebih sederhana

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
sumber
2
Jawaban sempurna untuk saya. Mungkin kata kunci 'fungsi' terlewatkan?
Stepan Loginov
28

Saya biasanya menggunakan paket npm melingkar-json untuk menyelesaikan ini.

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

Catatan: circular-json telah ditinggalkan, sekarang saya menggunakan flatted (dari pencipta 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"}]

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

pengguna3139574
sumber
8

Berdasarkan jawaban zainengineer ... Pendekatan lain adalah membuat salinan yang dalam dari objek dan menghapus referensi melingkar dan merangkai hasilnya.

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

Saya mengatasi masalah ini di NodeJS seperti ini:

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

Saya pernah mengalami kesalahan yang sama ketika mencoba membangun pesan di bawah ini dengan jQuery. Referensi melingkar terjadi ketika reviewerNameditugaskan secara keliru msg.detail.reviewerName. .Val () JQuery memperbaiki masalah, lihat baris terakhir.

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

Saya mendapatkan kesalahan yang sama dengan formvaliadator jQuery, tetapi ketika saya menghapus console.log di dalam success: function, itu berhasil.

Azmeer
sumber
0

Untuk kasus saya, saya mendapatkan kesalahan itu ketika saya menggunakan asyncfungsi di sisi server saya untuk mengambil dokumen menggunakan luwak. Ternyata alasannya adalah saya lupa memasukkan awaitsebelum memanggil find({})metode. Menambahkan bagian itu memperbaiki masalah saya.

Mussa Charles
sumber
0

Ini berfungsi dan memberi tahu Anda properti mana yang bundar. Ini juga memungkinkan untuk merekonstruksi objek dengan referensi

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

Contoh dengan banyak suara dihapus:

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

Untuk merekonstruksi panggilan JSON.parse (), kemudian loop melalui properti mencari [Circular Reference]tag. Kemudian potong itu dan ... eval ... denganthis set ke objek root.

Jangan evaluasi apa pun yang bisa diretas. Praktik yang lebih baik akan dilakukan string.split('.')kemudian mencari properti dengan nama untuk mengatur referensi.

Derek Ziemba
sumber