Serialisasi objek yang berisi nilai objek siklik

151

Saya memiliki objek (parse tree) yang berisi simpul anak yang merupakan referensi ke simpul lain.

Saya ingin membuat serial objek ini, menggunakan JSON.stringify(), tapi saya mengerti

TypeError: nilai objek siklik

karena konstruk yang saya sebutkan.

Bagaimana saya bisa mengatasi ini? Tidak masalah bagi saya apakah referensi ke node lain diwakili atau tidak dalam objek serial.

Di sisi lain, menghapus properti ini dari objek ketika sedang dibuat tampaknya membosankan dan saya tidak ingin membuat perubahan pada parser (narcissus).

Loic Duros
sumber
1
Kami tidak dapat membantu Anda tanpa kode. Silakan posting bit yang relevan dari objek Anda dan / atau output JSON bersama dengan JS yang Anda gunakan untuk membuat cerita bersambung.
Bojangles
1
apakah Anda dapat menambahkan beberapa awalan ke properti yang merupakan referensi internal?
wheresrhys
@ Loic Akan sangat berharga untuk memiliki cycle.jsjawaban Douglas Crockford di sini, karena ini adalah solusi yang paling tepat untuk banyak kasus. Tampaknya tepat bagi Anda untuk mengirim jawaban itu, karena Anda adalah orang pertama yang merujuknya (dalam komentar Anda di bawah). Jika Anda sendiri tidak ingin mempostingnya sebagai jawaban, saya akhirnya akan melakukannya.
Jeremy Banks
1
Saya berharap JSON akan lebih pintar, atau cara yang lebih mudah untuk menyelesaikan ini. Solusinya terlalu merepotkan untuk keperluan debugging (!) Sederhana imo.
BluE

Jawaban:

220

Gunakan parameter kedua dari stringify, fungsi replacer , untuk mengecualikan objek yang sudah serial:

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/

Seperti yang ditunjukkan dengan benar di komentar lain, kode ini menghapus setiap objek yang "terlihat", tidak hanya yang "rekursif".

Misalnya, untuk:

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

hasilnya akan salah. Jika struktur Anda seperti ini, Anda mungkin ingin menggunakan decycle Crockford atau fungsi (sederhana) ini yang hanya menggantikan referensi rekursif dengan nol:

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

georg
sumber
3
aaah bagus! Terima kasih, saya akan coba ini. Saya menemukan solusi yang dibuat oleh Douglas Crockford ( github.com/douglascrockford/JSON-js/blob/master/cycle.js ), tetapi karena saya tidak yakin dengan lisensi yang menyertainya, solusi mudah yang Anda gambarkan akan sempurna!
Loic Duros
3
@LoicDuros Lisensi adalah "domain publik". Artinya, Anda dapat melakukan apa pun yang Anda inginkan dengannya.
Ates Goral
1
kode ini menghasilkan loop bersepeda, hati-hati menggunakan, sangat berpotensi crash aplikasi Anda. membutuhkan titik koma yang benar dan tidak dapat digunakan pada objek acara!
Ol Sen
3
Ini menghapus lebih dari sekadar referensi siklik - hanya menghapus apa pun yang muncul lebih dari sekali. Kecuali jika objek yang sudah diserialisasi adalah "induk" dari objek baru, Anda tidak boleh menghapusnya
Gio
1
Jawaban yang bagus! Saya memodifikasi ini sedikit, mengubah fungsinya menjadi fungsi rekursif, sehingga objek anak akan dikloning seperti halnya objek induk dikloning.
HoldOffHunger
2

Saya telah membuat GitHub Gist yang dapat mendeteksi struktur siklik dan juga menghapus dan menyandikannya: https://gist.github.com/Hoff97/9842228

Untuk mentransformasikannya cukup gunakan JSONE.stringify / JSONE.parse. Itu juga de- dan mengkodekan fungsi. Jika Anda ingin menonaktifkan ini cukup hapus baris 32-48 dan 61-85.

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

Anda dapat menemukan contoh biola di sini:

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

Hoff
sumber
2

Ini semacam jawaban alternatif, tetapi karena banyak orang akan datang ke sini untuk men-debug objek melingkar mereka dan tidak ada cara yang bagus untuk melakukannya tanpa menarik banyak kode, begini saja.

Salah satu fitur yang tidak begitu terkenal JSON.stringify()adalah console.table(). Cukup panggil console.table(whatever);, dan itu akan mencatat variabel di konsol dalam format tabular, membuatnya cukup mudah dan nyaman untuk membaca dengan teliti isi variabel.

Andrew
sumber
1

lebih hemat dan ini menunjukkan di mana objek siklus berada.

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

menghasilkan

jsonify = {"g":{"d":[2,5],"j":2},"e":10,"someloopshere":[{"d":[2,5],"j":2},"__cycle__",{"a":[10,"__cycle__"]}]}
Ol Sen
sumber
tetapi masih ada masalah dengan kode ini jika seseorang akan membangun objek dengan obj.b=this'jika seseorang tahu bagaimana mencegah calcs yang sangat panjang terbuat dari ruang lingkup yang salah dengan thisakan lebih baik untuk melihat di sini
Ol Sen
2
Ini seharusnyaseen.indexOf(v) != -1
1

Saya membuat terlalu proyek github yang dapat membuat serial objek cyclic dan mengembalikan kelas jika Anda menyimpannya dalam atribut serializename seperti String

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

Sunting: Saya telah mengubah skrip saya untuk NPM https://github.com/bormat/borto_circular_serialize dan saya telah mengubah nama fungsi dari bahasa Prancis ke bahasa Inggris.

bormat
sumber
Contoh ini tidak sesuai dengan Intisari. Inti memiliki kesalahan.
Ernst Ernst
Ide bagus - tetapi setelah membuatnya siap :-) Jika Anda membuatnya didistribusikan dalam npm, mungkin Anda akan mengembangkan bahkan mengetik untuk itu, itu mungkin menjadi sangat populer.
peterh
1

Berikut adalah contoh dari struktur data dengan referensi siklik: toolshedCY

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

Ketika Anda ingin MENJAGA referensi siklik (kembalikan ketika Anda deserialize, daripada "nuking" mereka), Anda memiliki 2 pilihan, yang akan saya bandingkan di sini. Pertama adalah cycle.js Douglas Crockford , kedua adalah paket siberia saya . Keduanya bekerja dengan terlebih dahulu "mendekomposisi" objek, yaitu, membangun objek lain (tanpa referensi siklik) "yang mengandung informasi yang sama."

Mr. Crockford lebih dulu:

JSON.decycle(makeToolshed())

JSON_decycleMakeToolshed

Seperti yang Anda lihat, struktur bersarang JSON dipertahankan, tetapi ada hal baru, yaitu objek dengan $refproperti khusus . Mari kita lihat cara kerjanya.

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

Tanda dolar berarti root. .boltSetelah $refmemberitahu kita bahwa itu .boltadalah objek "sudah terlihat", dan nilai properti khusus itu (di sini, string $ ["nut"] ["needs"]) memberi tahu kita di mana, lihat pertama di ===atas. Begitu juga untuk yang kedua $refdan yang kedua di ===atas.

Mari kita gunakan tes kesetaraan mendalam yang sesuai (yaitu deepGraphEqualfungsi Anders Kaseorg dari jawaban yang diterima untuk pertanyaan ini ) untuk melihat apakah kloning bekerja.

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

Sekarang, siberia:

JSON.Siberia.forestify(makeToolshed())

JSON_Siberia_forestify_makeToolshed

Siberia tidak mencoba meniru JSON "klasik", tanpa struktur bersarang. Grafik objek dijelaskan dengan cara "datar". Setiap node dari objek grafik berubah menjadi flat tree (daftar pasangan nilai kunci biasa dengan nilai integer-only), yang merupakan entri di .forest.Pada indeks nol, kami menemukan objek root, pada indeks yang lebih tinggi, kami menemukan node lain dari grafik objek, dan nilai-nilai negatif (dari beberapa kunci dari beberapa pohon hutan) menunjuk ke atomsarray, (yang diketik melalui array jenis, tetapi kami akan melewatkan detail pengetikan di sini). Semua node terminal ada di tabel atom, semua node non-terminal ada di tabel hutan, dan Anda dapat melihat langsung berapa banyak node yang dimiliki grafik objek, yaitu forest.length. Mari kita coba jika berhasil:

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

perbandingan

akan menambahkan bagian nanti.

matematika seperti awan
sumber
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
    }
  );
}

Prasyarat hilang, jika tidak nilai integer dalam objek array terpotong, yaitu [[08.11.2014 12:30:13, 1095]] 1095 akan dikurangi menjadi 095.

pengguna3893329
sumber
mendapatkan RefrenceError: Tidak dapat menemukan variabel: _
amit pandya
Harap perbaiki kode Anda.
Anastasios Moraitis