การทำให้เป็นอันดับวัตถุที่มีค่าวัตถุวงจร


151

ฉันมีวัตถุ (แยกวิเคราะห์ต้นไม้) ที่มีโหนดลูกที่อ้างอิงถึงโหนดอื่น

ฉันต้องการทำให้เป็นวัตถุนี้โดยใช้JSON.stringify()แต่ฉันได้รับ

TypeError: ค่าของวัตถุที่เป็นวงจร

เพราะโครงสร้างที่ฉันพูดถึง

ฉันจะแก้ไขสิ่งนี้ได้อย่างไร ไม่สำคัญสำหรับฉันว่าการอ้างอิงเหล่านี้ไปยังโหนดอื่นถูกแทนหรือไม่ในวัตถุที่เป็นอนุกรม

ในทางกลับกันการลบคุณสมบัติเหล่านี้ออกจากวัตถุเมื่อมันถูกสร้างขึ้นดูน่าเบื่อและฉันไม่ต้องการเปลี่ยนแปลง parser (narcissus)


1
เราไม่สามารถช่วยคุณได้หากไม่มีรหัส โปรดโพสต์บิตที่เกี่ยวข้องของวัตถุและ / หรือเอาท์พุท JSON ของคุณพร้อมกับ JS ที่คุณใช้ในการทำให้มันเป็นอนุกรม
Bojangles

1
คุณสามารถเพิ่มคำนำหน้าให้กับคุณสมบัติเหล่านั้นซึ่งเป็นการอ้างอิงภายในได้หรือไม่?
เมื่อ

@Loic มันจะมีค่าถ้ามีcycle.jsคำตอบของ Douglas Crockford ที่นี่เพราะมันเป็นทางออกที่เหมาะสมที่สุดสำหรับหลาย ๆ กรณี ดูเหมือนจะเหมาะสมสำหรับคุณที่จะโพสต์คำตอบนั้นเนื่องจากคุณเป็นคนแรกที่จะอ้างอิง (ในความคิดเห็นของคุณด้านล่าง) หากคุณไม่อยากโพสต์เป็นคำตอบด้วยตัวเองฉันจะทำเช่นนั้นในที่สุด
Jeremy Banks


1
ฉันหวังว่า JSON จะฉลาดขึ้นหรือเป็นวิธีที่ง่ายกว่าในการแก้ปัญหานี้ การแก้ปัญหาลำบากเกินไปสำหรับจุดประสงค์ในการดีบักอย่างง่าย (!)
สีฟ้า

คำตอบ:


220

ใช้พารามิเตอร์ที่สองของstringifyการฟังก์ชั่นทดแทนเพื่อยกเว้นวัตถุต่อเนื่องอยู่แล้ว

var seen = [];

JSON.stringify(obj, function(key, val) {
   if (val != null && typeof val == "object") {
        if (seen.indexOf(val) >= 0) {
            return;
        }
        seen.push(val);
    }
    return val;
});

http://jsfiddle.net/mH6cJ/38/

ตามที่ระบุไว้อย่างถูกต้องในความคิดเห็นอื่น ๆ รหัสนี้จะลบทุกวัตถุ "เห็น" ไม่เพียง แต่ "ซ้ำ"

ตัวอย่างเช่นสำหรับ:

a = {x:1};
obj = [a, a];

ผลลัพธ์จะไม่ถูกต้อง หากโครงสร้างของคุณเป็นเช่นนี้คุณอาจต้องการใช้การถอดรหัสของ Crockford หรือฟังก์ชัน (ง่ายกว่า) ซึ่งเพิ่งแทนที่การอ้างอิงแบบเรียกซ้ำด้วย nulls:

function decycle(obj, stack = []) {
    if (!obj || typeof obj !== 'object')
        return obj;
    
    if (stack.includes(obj))
        return null;

    let s = stack.concat([obj]);

    return Array.isArray(obj)
        ? obj.map(x => decycle(x, s))
        : Object.fromEntries(
            Object.entries(obj)
                .map(([k, v]) => [k, decycle(v, s)]));
}

//

let a = {b: [1, 2, 3]}
a.b.push(a);

console.log(JSON.stringify(decycle(a)))


3
aaah ดี! ขอบคุณฉันจะลองสิ่งนี้ ฉันพบวิธีแก้ปัญหาที่สร้างขึ้นโดย Douglas Crockford ( github.com/douglascrockford/JSON-js/blob/master/cycle.js ) แต่เนื่องจากฉันไม่แน่ใจเกี่ยวกับสิทธิ์ใช้งานที่ไปด้วยโซลูชันที่ง่ายที่คุณอธิบายจะสมบูรณ์แบบ!
Loic Duros

3
@LoicDuros ใบอนุญาตคือ "โดเมนสาธารณะ" หมายความว่าคุณสามารถทำอะไรก็ได้ที่คุณต้องการด้วย
Ates Goral

1
รหัสนี้สร้างลูปการขี่จักรยานระวังการใช้งานอาจทำให้แอปของคุณขัดข้อง ต้องการอัฒภาคที่ถูกต้องและไม่สามารถใช้กับวัตถุเหตุการณ์ได้!
Ol Sen

3
นี่เป็นการลบมากกว่าการอ้างอิงแบบวนเท่านั้น - เพียงแค่ลบสิ่งที่ปรากฏมากกว่าหนึ่งครั้ง เว้นแต่วัตถุที่ได้รับการต่อเนื่องเป็น "หลัก" ของวัตถุใหม่คุณไม่ควรลบมัน
Gio

1
คำตอบที่ดี! ฉันปรับเปลี่ยนสิ่งนี้เล็กน้อยเปลี่ยนฟังก์ชั่นเป็นฟังก์ชั่นวนซ้ำเพื่อให้วัตถุเด็กจะได้รับการโคลนวิธีที่วัตถุแม่จะถูกโคลน
HoldOffHunger

2

ฉันได้สร้าง GitHub Gist ซึ่งสามารถตรวจจับโครงสร้างแบบวงกลมและยกเลิกและเข้ารหัส: https://gist.github.com/Hoff97/9842228

หากต้องการแปลงเพียงใช้ JSONE.stringify / JSONE.parse นอกจากนี้ยังยกเลิกและเข้ารหัสฟังก์ชั่น หากคุณต้องการปิดการใช้งานเพียงแค่ลบบรรทัด 32-48 และ 61-85

var strg = JSONE.stringify(cyclicObject);
var cycObject = JSONE.parse(strg);

คุณสามารถหาซอตัวอย่างได้ที่นี่:

http://jsfiddle.net/hoff97/7UYd4/


2

นี่เป็นคำตอบอื่น แต่เนื่องจากมีผู้คนมากมายมาที่นี่เพื่อทำการดีบั๊กวัตถุทรงกลมของพวกเขาและไม่มีวิธีที่ยอดเยี่ยมในการทำเช่นนั้นโดยไม่ต้องดึงโค้ดจำนวนมากมาเลย

หนึ่งคุณลักษณะที่ไม่ได้เป็นที่รู้จักกันดีคือJSON.stringify() console.table()เพียงแค่เรียกconsole.table(whatever);และมันจะบันทึกตัวแปรในคอนโซลในรูปแบบตารางทำให้ค่อนข้างง่ายและสะดวกในการอ่านเนื้อหาของตัวแปร


1

ประหยัดมากและมันแสดงให้เห็นว่าวัตถุวัฏจักรอยู่ที่ไหน

<script>
var jsonify=function(o){
    var seen=[];
    var jso=JSON.stringify(o, function(k,v){
        if (typeof v =='object') {
            if ( !seen.indexOf(v) ) { return '__cycle__'; }
            seen.push(v);
        } return v;
    });
    return jso;
};
var obj={
    g:{
        d:[2,5],
        j:2
    },
    e:10
};
obj.someloopshere = [
    obj.g,
    obj,
    { a: [ obj.e, obj ] }
];
console.log('jsonify=',jsonify(obj));
</script>

ผลิต

jsonify = {"g":{"d":[2,5],"j":2},"e":10,"someloopshere":[{"d":[2,5],"j":2},"__cycle__",{"a":[10,"__cycle__"]}]}

แต่ยังคงมีปัญหากับรหัสนี้ถ้ามีคนจะสร้างวัตถุที่มีobj.b=this'ถ้ามีคนรู้วิธีที่จะป้องกันไม่ให้ calcs นานมากทำจากขอบเขตที่ได้รับอย่างผิดปกติกับthisจะดีที่จะดูที่นี่
เฒ่าเสน

2
สิ่งนี้ควรเป็นseen.indexOf(v) != -1

1

ฉันสร้างโปรเจ็กต์ github ด้วยเช่นกันที่สามารถทำให้เป็นวัฏจักรวัตถุและเรียกคืนคลาสได้ถ้าคุณบันทึกในแอตทริบิวต์ serializename เช่นสตริง

var d={}
var a = {b:25,c:6,enfant:d};
d.papa=a;
var b = serializeObjet(a);
assert.equal(  b, "{0:{b:25,c:6,enfant:'tab[1]'},1:{papa:'tab[0]'}}" );
var retCaseDep = parseChaine(b)
assert.equal(  retCaseDep.b, 25 );
assert.equal(  retCaseDep.enfant.papa, retCaseDep );

https://github.com/bormat/serializeStringifyParseCyclicObject

แก้ไข: ฉันเปลี่ยนสคริปต์ของฉันเป็น NPM https://github.com/bormat/borto_circular_serializeและฉันเปลี่ยนชื่อฟังก์ชั่นจากภาษาฝรั่งเศสเป็นภาษาอังกฤษ


ตัวอย่างนี้ไม่เหมาะกับส่วนสำคัญ สรุปสาระสำคัญมีข้อผิดพลาด
Ernst Ernst

ความคิดที่ดี - แต่เมื่อเตรียมตัวให้พร้อม :-) ถ้าคุณจะทำให้มันเผยแพร่ในรอบต่อนาทีบางทีคุณอาจจะพัฒนารูปแบบที่เป็นแบบนั้นมันก็อาจจะได้รับความนิยม
peterh - Reinstate Monica

1

นี่คือตัวอย่างของโครงสร้างข้อมูลที่มีการอ้างอิงแบบวนซ้ำ: toolshedCY

function makeToolshed(){
    var nut = {name: 'nut'}, bolt = {name: 'bolt'};
    nut.needs = bolt; bolt.needs = nut;
    return { nut: nut, bolt: bolt };
}

เมื่อคุณต้องการเก็บการอ้างอิงแบบวนรอบ (คืนพวกเขาเมื่อคุณหมดความอดทนแทนที่จะเป็น "พวกนิก") คุณมี 2 ทางเลือกซึ่งฉันจะเปรียบเทียบที่นี่ อย่างแรกคือcycle.jsของ Douglas Crockford ส่วนที่สองคือแพ็คเกจไซบีเรียของฉัน ทั้งสองทำงานโดย "decycling" วัตถุแรกคือการสร้างวัตถุอื่น (โดยไม่มีการอ้างอิงแบบวนซ้ำ) "ที่มีข้อมูลเดียวกัน"

Mr. Crockford ไปก่อน:

JSON.decycle(makeToolshed())

JSON_decycleMakeToolshed

อย่างที่คุณเห็นโครงสร้างซ้อนของ JSON นั้นยังคงอยู่ แต่มีสิ่งใหม่ซึ่งเป็นวัตถุที่มี$refคุณสมบัติพิเศษ เรามาดูกันว่ามันทำงานอย่างไร

root = makeToolshed();
[root.bolt === root.nut.needs, root.nut.needs.needs === root.nut]; // retutrns [true,true]

เครื่องหมายดอลลาร์หมายถึงรูต .boltต้อง$refบอกเราว่า.boltเป็นวัตถุ "เห็นแล้ว" และค่าของคุณสมบัติพิเศษนั้น (ที่นี่สตริง $ ["nut"] ["ต้องการ"]) บอกเราว่าที่ใดดูก่อน===ข้างบน เช่นเดียวกันสำหรับวินาที$refและวินาที===ข้างต้น

ลองใช้การทดสอบความเท่าเทียมกันอย่างลึกซึ้งที่เหมาะสม (คือdeepGraphEqualฟังก์ชั่นของ Anders Kaseorg จากคำตอบที่ได้รับการยอมรับสำหรับคำถามนี้ ) เพื่อดูว่าการโคลนนิ่งทำงานหรือไม่

root = makeToolshed();
clone = JSON.retrocycle(JSON.decycle(root));
deepGraphEqual(root, clone) // true
serialized = JSON.stringify(JSON.decycle(root));
clone2 = JSON.retrocycle(JSON.parse(serialized));
deepGraphEqual(root, clone2); // true

ตอนนี้ไซบีเรีย:

JSON.Siberia.forestify(makeToolshed())

JSON_Siberia_forestify_makeToolshed

ไซบีเรียไม่พยายามเลียนแบบ JSON แบบ "คลาสสิค" ไม่มีโครงสร้างซ้อนกัน กราฟวัตถุอธิบายไว้ในลักษณะ "แบน" แต่ละโหนดของกราฟวัตถุถูกเปลี่ยนเป็นต้นไม้แบน (รายการคู่ค่าคีย์ธรรมดาที่มีค่าจำนวนเต็มเท่านั้น) ซึ่งเป็นรายการใน.forest.ศูนย์ดัชนีเราพบวัตถุรากที่ดัชนีที่สูงกว่าเราจะพบโหนดอื่น ๆ ของ กราฟวัตถุและค่าลบ (ของคีย์บางต้นของฟอเรสต์) ชี้ไปที่atomsอาร์เรย์ (ซึ่งพิมพ์ผ่านอาร์เรย์ประเภท แต่เราจะข้ามรายละเอียดการพิมพ์ที่นี่) forest.lengthทุกโหนดขั้วอยู่ในตารางอะตอมทุกโหนดที่ไม่ใช่ขั้วอยู่ในตารางป่าและคุณสามารถดูได้ทันทีว่าหลายโหนดกราฟวัตถุมีคือ ลองทดสอบว่ามันใช้งานได้ไหม:

root = makeToolshed();
clone = JSON.Siberia.unforestify(JSON.Siberia.forestify(root));
deepGraphEqual(root, clone); // true
serialized = JSON.Siberia.stringify(JSON.Siberia.forestify(root));
clone2 = JSON.Siberia.unforestify(JSON.Siberia.unstringify(serialized));
deepGraphEqual(root, clone2); // true

การเปรียบเทียบ

จะเพิ่มส่วนในภายหลัง


0
function stringifyObject ( obj ) {
  if ( _.isArray( obj ) || !_.isObject( obj ) ) {
    return obj.toString()
  }
  var seen = [];
  return JSON.stringify(
    obj,
    function( key, val ) {
      if (val != null && typeof val == "object") {
        if ( seen.indexOf( val ) >= 0 )
          return
          seen.push( val )
          }
      return val
    }
  );
}

ไม่มีเงื่อนไขก่อนหน้ามิฉะนั้นค่าจำนวนเต็มในวัตถุอาร์เรย์จะถูกตัดทอนนั่นคือ [[08.11.2014 12:30:13, 1095]] 1095 ลดลงเป็น 095


ได้รับ RefrenceError: ไม่พบตัวแปร: _
amit pandya

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