ข้อผิดพลาด sendrequest ของ 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 HERE" แต่จะโยนข้อผิดพลาดต่อไปนี้ขณะที่ทำงาน 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;
Felix Kling

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วัตถุเป็นวัตถุ?
asgs

3
@asgs: มันไม่ทำให้เกิดปัญหาอย่างน้อยใน Chrome Firefox ดูเหมือนจะฉลาดกว่านี้เล็กน้อย แต่ฉันไม่รู้ว่ามันกำลังทำอะไรอยู่
เฟลิกซ์คลิง

เป็นไปได้หรือไม่ที่จะ "จับ" ข้อผิดพลาดนี้และจัดการมันได้หรือไม่
Doug Molineux

2
@DougMolineux: แน่นอนคุณสามารถใช้try...catchเพื่อตรวจจับข้อผิดพลาดนี้
เฟลิกซ์ลิ่ง

4
@ FelixKling น่าเสียดายที่ฉันไม่สามารถทำงานได้ (อาจทำอะไรผิดพลาด) ฉันลงเอยด้วยการใช้สิ่งนี้: github.com/isaacs/json-stringify-safe
Doug Molineux

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
มันเป็นครั้งแรกที่ฉันเห็นฟังก์ชั่นที่ถูกส่งผ่านซึ่งจะส่งกลับฟังก์ชั่นการดำเนินการด้วยตนเองซึ่งจะส่งกลับฟังก์ชั่นปกติ ฉันเชื่อว่าฉันเข้าใจว่าเหตุใดจึงทำเช่นนี้ แต่ฉันไม่เชื่อว่าฉันจะพบวิธีแก้ปัญหาด้วยตัวเองและฉันรู้สึกว่าฉันสามารถจำเทคนิคนี้ได้ดีกว่าถ้าฉันเห็นตัวอย่างอื่น ๆ ที่จำเป็นต้องใช้การตั้งค่านี้ ที่ถูกกล่าวว่าคุณสามารถชี้วรรณกรรมใด ๆ ที่เกี่ยวกับการตั้งค่า / เทคนิคนี้ (สำหรับคำที่ดีกว่า) หรือที่คล้ายกัน?
Shawn

1
+1 ถึง Shawn โปรดลบ IEFE นั้นออกโดยไร้ประโยชน์และอ่านไม่ออกอย่างแน่นอน
Bergi

1
ขอบคุณสำหรับการชี้ให้เห็นถึงการเซ็นเซอร์หาเรื่อง! อนุญาตให้ทำการดีบักปัญหาแบบวงกลม ในกรณีของฉันฉันมีอาร์เรย์ jquery ที่ฉันเจ้าจะมีอาร์เรย์ปกติ พวกเขาทั้งสองมีลักษณะคล้ายกันในโหมดพิมพ์ debug เกี่ยวกับ IEFE ฉันเห็นพวกเขาใช้บ่อยในสถานที่ที่ไม่จำเป็นอย่างยิ่งสำหรับพวกเขาและเห็นด้วยกับ Shawn และ Bergi ว่านี่เป็นเพียงกรณีดังกล่าว
citykid

1
ฉันไม่แน่ใจว่าทำไม แต่วิธีนี้ไม่ได้ผลสำหรับฉัน
Nikola Schou

1
@BrunoLM: จำกัด จำนวนการวนซ้ำ 30 ครั้งหากคุณกลับมา'[Unknown:' + typeof(value) + ']'คุณจะเห็นวิธีแก้ไขเซ็นเซอร์เพื่อใช้งานฟังก์ชั่นและประเภทอื่น ๆ อย่างเหมาะสม
Alex Pakka

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
คำตอบที่สมบูรณ์แบบสำหรับฉัน บางทีคำหลัก 'ฟังก์ชั่น' อาจพลาด
Stepan Loginov

28

ปกติฉันจะใช้แพคเกจ Circular-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.reviewerNameJQuery's .val () แก้ไขปัญหาดูบรรทัดสุดท้าย

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 formvaliadator แต่เมื่อฉันลบ 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('.')ค้นหาคุณสมบัติตามชื่อเพื่อตั้งค่าการอ้างอิง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.