Bagaimana saya bisa mencetak struktur melingkar dalam format seperti JSON?


681

Saya memiliki objek besar yang ingin saya konversi ke JSON dan kirim. Namun memiliki struktur melingkar. Saya ingin melemparkan referensi melingkar apa pun yang ada dan mengirim apa pun yang dapat dirangkum. Bagaimana aku melakukan itu?

Terima kasih.

var obj = {
  a: "foo",
  b: obj
}

Saya ingin menjabarkan obj ke:

{"a":"foo"}

5
Bisakah Anda memposting objek sampel dengan referensi melingkar yang ingin Anda parsing?
TWickz

3
sesuatu seperti ini ?
Alvin Wong


2
Terlambat ke pesta tetapi ada proyek github untuk menangani ini.
Preston S

Jawaban:


607

Gunakan JSON.stringifydengan replacer khusus. Sebagai contoh:

// Demo: Circular reference
var circ = {};
circ.circ = circ;

// Note: cache should not be re-used by repeated calls to JSON.stringify.
var cache = [];
JSON.stringify(circ, (key, value) => {
  if (typeof value === 'object' && value !== null) {
    // Duplicate reference found, discard key
    if (cache.includes(value)) return;

    // Store value in our collection
    cache.push(value);
  }
  return value;
});
cache = null; // Enable garbage collection

Pengganti dalam contoh ini tidak 100% benar (tergantung pada definisi "duplikat" Anda). Dalam kasus berikut, nilai dibuang:

var a = {b:1}
var o = {};
o.one = a;
o.two = a;
// one and two point to the same object, but two is discarded:
JSON.stringify(o, ...);

Tapi konsepnya tetap: Gunakan custom replacer, dan pantau nilai objek yang diuraikan.

Sebagai fungsi utilitas yang ditulis dalam es6:

// safely handles circular references
JSON.safeStringify = (obj, indent = 2) => {
  let cache = [];
  const retVal = JSON.stringify(
    obj,
    (key, value) =>
      typeof value === "object" && value !== null
        ? cache.includes(value)
          ? undefined // Duplicate reference found, discard key
          : cache.push(value) && value // Store value in our collection
        : value,
    indent
  );
  cache = null;
  return retVal;
};

// Example:
console.log('options', JSON.safeStringify(options))

1
@ Harry, Apa masalahnya? Saya akan dengan senang hati memperbaiki jawabannya, jika ada ketidakakuratan di dalamnya.
Rob W

1
@CruzDiablo Serializing DOM biasanya tidak ada artinya. Namun, jika Anda dapat memikirkan metode serialisasi yang bermakna untuk keperluan Anda, maka Anda dapat mencoba menambahkan custom serial ke objek DOM: Node.prototype.toJSON = function() { return 'whatever you think that is right'; };(jika Anda ingin sesuatu yang lebih umum / spesifik, coba saja apa saja di pohon prototipe: HTMLDivElement mengimplementasikan HTMLElement mengimplementasikan implementements Elemen mengimplementasikan Node mengimplementasikan EventTarget; catatan: ini mungkin tergantung pada browser, pohon sebelumnya berlaku untuk Chrome)
Rob W

7
ini salah karena akan melewatkan penampilan kedua benda yang terkandung dua kali, bahkan jika tidak dalam struktur yang benar-benar siklik. var a={id:1}; JSON.stringify([a,a]);
user2451227

3
@ user2451227 "Pengganti dalam contoh ini tidak 100% benar (tergantung pada definisi Anda tentang" duplikat "). Tetapi konsepnya berdiri: Gunakan pengganti kustom, dan pantau nilai objek yang diuraikan."
Rob W

4
Kekhawatiran GC di sini bisa dibilang berlebihan. Jika ini dijalankan sebagai satu skrip maka skrip segera berakhir. Jika ini dienkapsulasi di dalam fungsi untuk implementasi maka cacheakan menjadi developer.mozilla.org/en-US/docs/Web/JavaScript/… yang
Trindaz

704

Di Node.js, Anda dapat menggunakan util.inspect (objek) . Secara otomatis menggantikan tautan sirkuler dengan "[Circular]".


Meskipun sudah terpasang (tidak diperlukan instalasi) , Anda harus mengimpornya

import * as util from 'util' // has no default export
import { inspect } from 'util' // or directly
// or 
var util = require('util')
Untuk menggunakannya, cukup panggil
console.log(util.inspect(myObject))

Perlu diketahui juga bahwa Anda dapat melewatkan objek opsi untuk diperiksa (lihat tautan di atas)

inspect(myObject[, options: {showHidden, depth, colors, showProxy, ...moreOptions}])



Tolong, baca dan berikan pujian kepada komentator di bawah ...


134
util adalah modul bawaan, Anda tidak harus menginstalnya.
Mitar

10
console.log (util.inspect (obj))
starsinmypockets

19
@Mitar sudah built-in, tetapi Anda masih harus memuat modulvar util = require('util');
bodecker

14
Jangan bodoh seperti saya, itu hanya obj_str = util.inspect(thing) , BUKAN <s> garbage_str = JSON.stringify(util.inspect(thing))</s>
ThorSummoner

7
Ini jauh lebih baik daripada mucking sekitar dengan memeriksa jenis. Mengapa tidak bisa hanya bekerja seperti ini? Jika tahu ada referensi melingkar, mengapa tidak bisa disuruh mengabaikannya ???
Chris Peacock

141

Saya heran mengapa belum ada yang memposting solusi yang tepat dari halaman MDN ...

const getCircularReplacer = () => {
  const seen = new WeakSet();
  return (key, value) => {
    if (typeof value === "object" && value !== null) {
      if (seen.has(value)) {
        return;
      }
      seen.add(value);
    }
    return value;
  };
};

JSON.stringify(circularReference, getCircularReplacer());

Nilai yang terlihat harus disimpan dalam satu set , bukan dalam array (replacer dipanggil pada setiap elemen ) dan tidak perlu untuk mencoba JSON.stringify setiap elemen dalam rantai yang mengarah ke referensi melingkar.

Seperti dalam jawaban yang diterima, solusi ini menghapus semua nilai berulang , bukan hanya yang melingkar. Tetapi setidaknya tidak memiliki kompleksitas eksponensial.


Rapi, tapi ini hanya ES2015. Tidak ada dukungan IE.
Martin Capodici

43
Yoda mengatakan: "Jika masih mendukung IE satu, maka gunakan transpiler satu harus."
Kereta Spanyol

1
replacer = () => { const seen = new WeakSet(); return (key, value) => { if (typeof value === "object" && value !== null) { if (seen.has(value)) { return; } seen.add(value); } return value; }; } () => { const seen = new WeakSet(); return (key, value) => { if (typeof value === "object" && value !== null) { if (seen.has(value)) { return; } seen.add(value); … JSON.stringify({a:1, b: '2'}, replacer)kembali undefineddalam chrome
roberto tomás

1
Ini bekerja di Bereaksi + naskah. terima kasih
user3417479

76

kerjakan saja

npm i --save circular-json

kemudian di file js Anda

const CircularJSON = require('circular-json');
...
const json = CircularJSON.stringify(obj);

https://github.com/WebReflection/circular-json

CATATAN: Saya tidak ada hubungannya dengan paket ini. Tapi saya menggunakannya untuk ini.

Perbarui 2020

Harap dicatat CircularJSON hanya dalam pemeliharaan dan flatted adalah penggantinya.


Terima kasih banyak! Perpustakaan yang bagus, menghemat banyak waktu. Super kecil (hanya 1.4KB yang diperkecil).
Brian Haak

16
Saya pikir Anda mungkin memerlukan lebih banyak pembenaran untuk menggunakan modul daripada "just do". Dan tidak bagus untuk menimpa JSONpada prinsipnya.
Edwin

Saya perlu menyalin objek yang akan digunakan untuk pengujian rintisan. Jawaban ini sempurna. Saya menyalin objek dan kemudian menghapus override. Terima kasih!!
Chris Sharp

1
Menurut penulis, paket ini sudah usang. CircularJSON hanya dalam pemeliharaan, flatted adalah penggantinya. Tautan: github.com/WebReflection/flatted#flatted
Robert Molina

3
Hati-hati, paket 'flatted' (dan circular-json?) Tidak mereplikasi fungsi JSON.stringify (). Ini menciptakan format non-JSON sendiri. (misalnya, Flatted.stringify({blah: 1})hasil [{"blah":1}]) Saya melihat seseorang mencoba mengangkat masalah tentang ini, dan penulis mencaci mereka dan mengunci masalah menjadi komentar.
jameslol

48

Saya sangat menyukai solusi Trindaz - lebih verbose, tetapi memiliki beberapa bug. Saya memperbaikinya untuk siapa pun yang menyukainya.

Plus, saya menambahkan batas panjang pada objek cache saya.

Jika objek yang saya cetak sangat besar - maksud saya sangat besar - saya ingin membatasi algoritma saya.

JSON.stringifyOnce = function(obj, replacer, indent){
    var printedObjects = [];
    var printedObjectKeys = [];

    function printOnceReplacer(key, value){
        if ( printedObjects.length > 2000){ // browsers will not print more than 20K, I don't see the point to allow 2K.. algorithm will not be fast anyway if we have too many objects
        return 'object too long';
        }
        var printedObjIndex = false;
        printedObjects.forEach(function(obj, index){
            if(obj===value){
                printedObjIndex = index;
            }
        });

        if ( key == ''){ //root element
             printedObjects.push(obj);
            printedObjectKeys.push("root");
             return value;
        }

        else if(printedObjIndex+"" != "false" && typeof(value)=="object"){
            if ( printedObjectKeys[printedObjIndex] == "root"){
                return "(pointer to root)";
            }else{
                return "(see " + ((!!value && !!value.constructor) ? value.constructor.name.toLowerCase()  : typeof(value)) + " with key " + printedObjectKeys[printedObjIndex] + ")";
            }
        }else{

            var qualifiedKey = key || "(empty key)";
            printedObjects.push(value);
            printedObjectKeys.push(qualifiedKey);
            if(replacer){
                return replacer(key, value);
            }else{
                return value;
            }
        }
    }
    return JSON.stringify(obj, printOnceReplacer, indent);
};

Anda melewatkan cek nol pada baris ini: return "(lihat" + (!! value.constructor? Value.constructor.name.toLowerCase (): typeof (value)) + "dengan kunci" + printedObjectKeys [printedObjIndex] + ")";
Isak

Saya dengan senang hati akan menambahkannya. beri tahu saya apa yang bisa dibatalkan karena saya mengalami masalah sejauh ini.
guy mograbi

2
// peramban tidak akan mencetak lebih dari 20K - Tapi Anda menetapkan batas sebagai 2k. Mungkin berubah untuk masa depan?
Pochen

38

@Jawab RobW benar, tetapi ini lebih berkinerja! Karena menggunakan hashmap / set:

const customStringify = function (v) {
  const cache = new Set();
  return JSON.stringify(v, function (key, value) {
    if (typeof value === 'object' && value !== null) {
      if (cache.has(value)) {
        // Circular reference found
        try {
          // If this value does not reference a parent it can be deduped
         return JSON.parse(JSON.stringify(value));
        }
        catch (err) {
          // discard key if value cannot be deduped
         return;
        }
      }
      // Store value in our set
      cache.add(value);
    }
    return value;
  });
};

Untuk objek yang sangat bersarang dengan referensi melingkar, coba stringifyDeep => github.com/ORESoftware/safe-stringify
Alexander Mills

Mungkin implementasi Set hanya menggunakan array dan indexOf di bawah tenda, tapi saya belum mengonfirmasi hal itu.
Alexander Mills

Ini menghapus node induk yang memiliki node anak bahkan dengan nilai yang berbeda - misalnya - {"a":{"b":{"a":"d"}}}dan bahkan menghapus node yang memiliki objek kosong {}
Sandip Pingle

Bisakah Anda menunjukkan contoh Sandip itu? buat gist.github.com atau yang lainnya
Alexander Mills

Luar Biasa !!! Pertama (dari atas, tetapi hanya memeriksa 2-3 solusi fungsi) solusi yang bekerja di sini di bawah node.js dan Fission ;-) - perpustakaan digantung.
Tom

37

Perhatikan bahwa ada juga JSON.decyclemetode yang diterapkan oleh Douglas Crockford. Lihat cycle.js- nya . Ini memungkinkan Anda untuk merangkai hampir semua struktur standar:

var a = [];
a[0] = a;
a[1] = 123;
console.log(JSON.stringify(JSON.decycle(a)));
// result: '[{"$ref":"$"},123]'.

Anda juga dapat membuat ulang objek asli dengan retrocyclemetode. Jadi, Anda tidak harus menghapus siklus dari objek untuk mengencangkannya.

Namun ini tidak akan berfungsi untuk DOM Nodes (yang merupakan penyebab khas siklus dalam kasus penggunaan kehidupan nyata). Misalnya ini akan melempar:

var a = [document.body];
console.log(JSON.stringify(JSON.decycle(a)));

Saya telah membuat garpu untuk menyelesaikan masalah itu (lihat garpu cycle.js saya ). Ini seharusnya bekerja dengan baik:

var a = [document.body];
console.log(JSON.stringify(JSON.decycle(a, true)));

Perhatikan bahwa dalam garpu saya JSON.decycle(variable)berfungsi seperti pada aslinya dan akan melemparkan pengecualian ketikavariable mengandung DOM node / elemen.

Ketika Anda menggunakan, JSON.decycle(variable, true)Anda menerima kenyataan bahwa hasilnya tidak akan dapat dibalikkan (retrocycle tidak akan membuat kembali node DOM). Elemen DOM harus dapat diidentifikasi sampai batas tertentu. Misalnya jika suatu divelemen memiliki id maka itu akan diganti dengan string "div#id-of-the-element".


2
Baik kodenya dan milik Anda memberi saya "RangeError: Ukuran stack panggilan maksimum terlampaui" ketika saya menggunakannya.
jcollum

Saya dapat melihatnya jika Anda memberikan kode pada Fiddle atau menambahkan masalah pada Github: github.com/Eccenux/JSON-js/issues
Nux

Ini yang saya cari. JSON.decycle(a, true)apa yang terjadi ketika Anda meneruskan true sebagai parameter untuk mendaur ulang fungsi.
Rudra

@Rudra true menjadikan stringifyNodesopsi benar dalam garpu. Ini akan membuang misalnya divdengan id = "beberapa-id" string: div#some-id. Anda akan menghindari beberapa masalah, tetapi Anda tidak akan dapat sepenuhnya retro-cycle.
Nux

Ada paket npm npmjs.com/package/json-js , tetapi tidak diperbarui untuk sementara waktu
Michael Freidgeim

23

Saya akan merekomendasikan memeriksa json-stringify-safe dari @ isaacs-- ini digunakan dalam NPM.

BTW- jika Anda tidak menggunakan Node.js, Anda bisa menyalin dan menempelkan baris 4-27 dari bagian kode sumber yang relevan .

Untuk memasang:

$ npm install json-stringify-safe --save

Menggunakan:

// Require the thing
var stringify = require('json-stringify-safe');

// Take some nasty circular object
var theBigNasty = {
  a: "foo",
  b: theBigNasty
};

// Then clean it up a little bit
var sanitized = JSON.parse(stringify(theBigNasty));

Ini menghasilkan:

{
  a: 'foo',
  b: '[Circular]'
}

Perhatikan bahwa, seperti halnya fungsi vanilla JSON.stringify seperti yang disebutkan oleh @Rob W, Anda juga dapat menyesuaikan perilaku sanitasi dengan mengirimkan fungsi "replacer" sebagai argumen kedua stringify(). Jika Anda membutuhkan contoh sederhana tentang cara melakukan ini, saya baru saja menulis custom replacer yang memaksa kesalahan, regexps, dan fungsi menjadi string yang dapat dibaca manusia di sini .


13

Untuk googler di masa depan mencari solusi untuk masalah ini ketika Anda tidak tahu kunci dari semua referensi melingkar, Anda bisa menggunakan pembungkus di sekitar fungsi JSON.stringify untuk mengesampingkan referensi melingkar. Lihat contoh skrip di https://gist.github.com/4653128 .

Solusinya pada dasarnya bermuara pada menjaga referensi ke objek yang sebelumnya dicetak dalam array, dan memeriksa bahwa dalam fungsi replacer sebelum mengembalikan nilai. Ini lebih konstriksi daripada hanya mengesampingkan referensi melingkar, karena itu juga mengesampingkan pernah mencetak objek dua kali, salah satu sisi yang mempengaruhi adalah untuk menghindari referensi melingkar.

Pembungkus contoh:

function stringifyOnce(obj, replacer, indent){
    var printedObjects = [];
    var printedObjectKeys = [];

    function printOnceReplacer(key, value){
        var printedObjIndex = false;
        printedObjects.forEach(function(obj, index){
            if(obj===value){
                printedObjIndex = index;
            }
        });

        if(printedObjIndex && typeof(value)=="object"){
            return "(see " + value.constructor.name.toLowerCase() + " with key " + printedObjectKeys[printedObjIndex] + ")";
        }else{
            var qualifiedKey = key || "(empty key)";
            printedObjects.push(value);
            printedObjectKeys.push(qualifiedKey);
            if(replacer){
                return replacer(key, value);
            }else{
                return value;
            }
        }
    }
    return JSON.stringify(obj, printOnceReplacer, indent);
}

3
Kode yang bagus Anda memiliki kesalahan konyol, Anda menulis if(printedObjIndex)sementara Anda harus menulis if(printedObjIndex==false)karena indexbisa juga 0yang diterjemahkan falsekecuali Anda secara eksplisit menyatakan sebaliknya.
guy mograbi

1
@guymograbi, bukan ===? 0 == falseadalah true, 0 === falseadalah false. ; ^) Tapi saya lebih suka tidak menginisialisasi printedObjIndexke false, karena Anda dapat memeriksa undefinedsehingga Anda (well, Trindaz's) tidak mencampur metafora aneh.
ruffin

@ruffin tangkapan yang bagus. ya jelas, selalu gunakan kesetaraan keras dan jshint untuk menangkap kesalahan konyol seperti itu.
guy mograbi

4

Gunakan metode JSON.stringify dengan replacer. Baca dokumentasi ini untuk informasi lebih lanjut. http://msdn.microsoft.com/en-us/library/cc836459%28v=vs.94%29.aspx

var obj = {
  a: "foo",
  b: obj
}

var replacement = {"b":undefined};

alert(JSON.stringify(obj,replacement));

Mencari cara untuk mengisi array pengganti dengan referensi siklik. Anda dapat menggunakan metode typeof untuk menemukan apakah properti bertipe 'objek' (referensi) dan pemeriksaan kesetaraan yang tepat (===) untuk memverifikasi referensi melingkar.


4
Ini mungkin hanya berfungsi di IE (mengingat fakta bahwa MSDN adalah dokumentasi dari Microsoft, dan Microsoft menciptakan IE). Di Firefox / Chrome, jsfiddle.net/ppmaW menghasilkan kesalahan referensi melingkar. FYI: var obj = {foo:obj}tidak tidak membuat referensi melingkar. Alih-alih, ia membuat objek yang fooatributnya merujuk pada nilai sebelumnya obj( undefinedjika tidak ditentukan sebelumnya, dideklarasikan karena var obj).
Rob W

4

Jika

console.log(JSON.stringify(object));

menghasilkan a

TypeError: nilai objek siklik

Maka Anda mungkin ingin mencetak seperti ini:

var output = '';
for (property in object) {
  output += property + ': ' + object[property]+'; ';
}
console.log(output);

21
Mungkin karena hanya mencetak satu level?
Alex Turpin

SANGAT SEDERHANA saya membatalkan ini karena itu bekerja untuk saya di luar kotak di chrome. SANGAT BAIK
Cinta dan kedamaian - Joe Codeswell

4
var a={b:"b"};
a.a=a;
JSON.stringify(preventCircularJson(a));

mengevaluasi ke:

"{"b":"b","a":"CIRCULAR_REFERENCE_REMOVED"}"

dengan fungsi:

/**
 * Traverses a javascript object, and deletes all circular values
 * @param source object to remove circular references from
 * @param censoredMessage optional: what to put instead of censored values
 * @param censorTheseItems should be kept null, used in recursion
 * @returns {undefined}
 */
function preventCircularJson(source, censoredMessage, censorTheseItems) {
    //init recursive value if this is the first call
    censorTheseItems = censorTheseItems || [source];
    //default if none is specified
    censoredMessage = censoredMessage || "CIRCULAR_REFERENCE_REMOVED";
    //values that have allready apeared will be placed here:
    var recursiveItems = {};
    //initaite a censored clone to return back
    var ret = {};
    //traverse the object:
    for (var key in source) {
        var value = source[key]
        if (typeof value == "object") {
            //re-examine all complex children again later:
            recursiveItems[key] = value;
        } else {
            //simple values copied as is
            ret[key] = value;
        }
    }
    //create list of values to censor:
    var censorChildItems = [];
    for (var key in recursiveItems) {
        var value = source[key];
        //all complex child objects should not apear again in children:
        censorChildItems.push(value);
    }
    //censor all circular values
    for (var key in recursiveItems) {
        var value = source[key];
        var censored = false;
        censorTheseItems.forEach(function (item) {
            if (item === value) {
                censored = true;
            }
        });
        if (censored) {
            //change circular values to this
            value = censoredMessage;
        } else {
            //recursion:
            value = preventCircularJson(value, censoredMessage, censorChildItems.concat(censorTheseItems));
        }
        ret[key] = value

    }

    return ret;
}

3

Saya tahu ini adalah pertanyaan lama, tetapi saya ingin menyarankan paket NPM yang saya buat disebut smart-circular , yang bekerja secara berbeda dari cara lain yang diusulkan. Ini sangat berguna jika Anda menggunakan benda besar dan dalam .

Beberapa fitur adalah:

  • Mengganti referensi melingkar atau hanya mengulangi struktur di dalam objek dengan jalan menuju kemunculan pertamanya (bukan hanya string [melingkar] );

  • Dengan mencari bundaran dalam pencarian pertama yang luas, paket memastikan jalur ini sekecil mungkin, yang penting ketika berhadapan dengan objek yang sangat besar dan dalam, di mana jalur bisa menjadi panjang dan sulit untuk diikuti (penggantian kustom di JSON.stringify melakukan DFS);

  • Mengizinkan penggantian yang dipersonalisasi, berguna untuk menyederhanakan atau mengabaikan bagian objek yang kurang penting;

  • Akhirnya, jalur ditulis persis seperti yang diperlukan untuk mengakses bidang yang dirujuk, yang dapat membantu Anda melakukan debug.


3

Argumen kedua untuk JSON.stringify () juga memungkinkan Anda untuk menentukan array nama kunci yang harus dipertahankan dari setiap objek yang ditemui dalam data Anda. Ini mungkin tidak berfungsi untuk semua kasus penggunaan, tetapi merupakan solusi yang jauh lebih sederhana.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify

var obj = {
    a: "foo",
    b: this
}

var json = JSON.stringify(obj, ['a']);
console.log(json);
// {"a":"foo"}

Catatan: Anehnya, definisi objek dari OP tidak menimbulkan kesalahan referensi melingkar di Chrome atau Firefox terbaru. Definisi dalam jawaban ini telah dimodifikasi sehingga tidak menimbulkan kesalahan.



Jawaban yang ini harus diterima
Manic Depression

2

Untuk memperbarui jawaban untuk mengganti cara kerja JSON (mungkin tidak disarankan, tetapi sangat sederhana), jangan gunakan circular-json(sudah usang). Alih-alih, gunakan penerusnya, yang diratakan:

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

Dipinjam dari jawaban lama di atas dari @ user1541685, tetapi diganti dengan yang baru:

npm i --save flatted

kemudian di file js Anda

const CircularJSON = require('flatted');
const json = CircularJSON.stringify(obj);

1

Saya menemukan perpustakaan melingkar-json di github dan itu bekerja dengan baik untuk masalah saya.

Beberapa fitur bagus yang menurut saya berguna:

  • Mendukung penggunaan multi-platform tetapi saya hanya mengujinya dengan node.js sejauh ini.
  • API adalah sama sehingga yang perlu Anda lakukan hanyalah memasukkan dan menggunakannya sebagai pengganti JSON.
  • Ini memiliki metode parsing sendiri sehingga Anda dapat mengkonversi data serial 'melingkar' kembali ke objek.

2
Perpustakaan ini membuat kesalahan bagi saya jadi saya harus mencari yang lain. ERROR TypeError: toISOString bukan fungsi di String.toJSON (<anonymous>) di Object. <anonymous> ( localhost: 8100 / build / polyfills.js: 1: 3458 ) di JSON.stringify (<anonymous>) di Object. stringifyRecursion [as stringify] ( localhost: 8100 / build / main.js: 258450: 15 )
Mark Ellul

1
@MarkEllul Saya sudah menulis komentar pada tahun 2015 dan jika saya akan melihat alternatif yang lebih baik saya akan memposting di sini dengan sunting. Saya masih berakhir dengan masalah yang sama dalam pekerjaan sehari-hari kadang-kadang dan saya biasanya lebih suka fungsi manual saya secara rekursif dengan inspeksi yang tepat / aman. Saya akan menyarankan memeriksa praktik pemrograman fungsional jika Anda tidak terbiasa, biasanya, ini memudahkan operasi rekursif seperti ini menjadi kurang rumit dan lebih dapat diandalkan.
JacopKane

Juga mendapatkan "toISOString bukan suatu fungsi" yang mencoba untuk mengencangkan suatu peristiwa dan mengirimkannya kembali dalam tes cypress
Devin G Rhode

1

Saya mengatasi masalah ini 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));

Ini cukup banyak bekerja untuk saya tetapi sepertinya kelas diwakili _class: ClassName { data: "here" }, jadi saya menambahkan aturan berikut .replace(/(\w+) {/g, '{ __ClassName__: "$1", '). Dalam kasus saya, saya mencoba untuk melihat seperti apa objek permintaan http.
redbmk

1

Saya tahu pertanyaan ini sudah lama dan memiliki banyak jawaban, tetapi saya memposting jawaban ini karena rasanya yang baru (es5 +)


1

Meskipun ini telah dijawab dengan cukup, Anda juga dapat secara eksplisit menghapus properti yang dipertanyakan sebelum pengerasan menggunakan deleteoperator.

delete obj.b; 
const jsonObject = JSON.stringify(obj);

hapus operator

ini akan menghapus kebutuhan untuk membangun atau mempertahankan logika kompleks untuk menghapus referensi melingkar.


1
function myStringify(obj, maxDeepLevel = 2) {
  if (obj === null) {
    return 'null';
  }
  if (obj === undefined) {
    return 'undefined';
  }
  if (maxDeepLevel < 0 || typeof obj !== 'object') {
    return obj.toString();
  }
  return Object
    .entries(obj)
    .map(x => x[0] + ': ' + myStringify(x[1], maxDeepLevel - 1))
    .join('\r\n');
}


0

Berdasarkan jawaban lain saya berakhir dengan kode berikut. Ini bekerja cukup baik dengan referensi melingkar, objek dengan konstruktor khusus.

Dari objek yang diberikan untuk diserialisasi,

  • Tembolok semua objek yang Anda temui saat melintasi objek dan berikan masing-masing hashID unik (nomor penambahan otomatis juga berfungsi)
  • Setelah referensi melingkar ditemukan, tandai bidang itu di objek baru sebagai lingkaran dan simpan hashID objek asli sebagai atribut.

Github Link - DecycledJSON

DJSHelper = {};
DJSHelper.Cache = [];
DJSHelper.currentHashID = 0;
DJSHelper.ReviveCache = [];

// DOES NOT SERIALIZE FUNCTION
function DJSNode(name, object, isRoot){
    this.name = name;
    // [ATTRIBUTES] contains the primitive fields of the Node
    this.attributes = {};

    // [CHILDREN] contains the Object/Typed fields of the Node
    // All [CHILDREN] must be of type [DJSNode]
    this.children = []; //Array of DJSNodes only

    // If [IS-ROOT] is true reset the Cache and currentHashId
    // before encoding
    isRoot = typeof isRoot === 'undefined'? true:isRoot;
    this.isRoot = isRoot;
    if(isRoot){
        DJSHelper.Cache = [];
        DJSHelper.currentHashID = 0;

        // CACHE THE ROOT
        object.hashID = DJSHelper.currentHashID++;
        DJSHelper.Cache.push(object);
    }

    for(var a in object){
        if(object.hasOwnProperty(a)){
            var val = object[a];

            if (typeof val === 'object') {
                // IF OBJECT OR NULL REF.

                /***************************************************************************/
                // DO NOT REMOVE THE [FALSE] AS THAT WOULD RESET THE [DJSHELPER.CACHE]
                // AND THE RESULT WOULD BE STACK OVERFLOW
                /***************************************************************************/
                if(val !== null) {
                    if (DJSHelper.Cache.indexOf(val) === -1) {
                        // VAL NOT IN CACHE
                        // ADD THE VAL TO CACHE FIRST -> BEFORE DOING RECURSION
                        val.hashID = DJSHelper.currentHashID++;
                        //console.log("Assigned", val.hashID, "to", a);
                        DJSHelper.Cache.push(val);

                        if (!(val instanceof Array)) {
                            // VAL NOT AN [ARRAY]
                            try {
                                this.children.push(new DJSNode(a, val, false));
                            } catch (err) {
                                console.log(err.message, a);
                                throw err;
                            }
                        } else {
                            // VAL IS AN [ARRAY]
                            var node = new DJSNode(a, {
                                array: true,
                                hashID: val.hashID // HashID of array
                            }, false);
                            val.forEach(function (elem, index) {
                                node.children.push(new DJSNode("elem", {val: elem}, false));
                            });
                            this.children.push(node);
                        }
                    } else {
                        // VAL IN CACHE
                        // ADD A CYCLIC NODE WITH HASH-ID
                        this.children.push(new DJSNode(a, {
                            cyclic: true,
                            hashID: val.hashID
                        }, false));
                    }
                }else{
                    // PUT NULL AS AN ATTRIBUTE
                    this.attributes[a] = 'null';
                }
            } else if (typeof val !== 'function') {
                // MUST BE A PRIMITIVE
                // ADD IT AS AN ATTRIBUTE
                this.attributes[a] = val;
            }
        }
    }

    if(isRoot){
        DJSHelper.Cache = null;
    }
    this.constructorName = object.constructor.name;
}
DJSNode.Revive = function (xmlNode, isRoot) {
    // Default value of [isRoot] is True
    isRoot = typeof isRoot === 'undefined'?true: isRoot;
    var root;
    if(isRoot){
        DJSHelper.ReviveCache = []; //Garbage Collect
    }
    if(window[xmlNode.constructorName].toString().indexOf('[native code]') > -1 ) {
        // yep, native in the browser
        if(xmlNode.constructorName == 'Object'){
            root = {};
        }else{
            return null;
        }
    }else {
        eval('root = new ' + xmlNode.constructorName + "()");
    }

    //CACHE ROOT INTO REVIVE-CACHE
    DJSHelper.ReviveCache[xmlNode.attributes.hashID] = root;

    for(var k in xmlNode.attributes){
        // PRIMITIVE OR NULL REF FIELDS
        if(xmlNode.attributes.hasOwnProperty(k)) {
            var a = xmlNode.attributes[k];
            if(a == 'null'){
                root[k] = null;
            }else {
                root[k] = a;
            }
        }
    }

    xmlNode.children.forEach(function (value) {
        // Each children is an [DJSNode]
        // [Array]s are stored as [DJSNode] with an positive Array attribute
        // So is value

        if(value.attributes.array){
            // ITS AN [ARRAY]
            root[value.name] = [];
            value.children.forEach(function (elem) {
                root[value.name].push(elem.attributes.val);
            });
            //console.log("Caching", value.attributes.hashID);
            DJSHelper.ReviveCache[value.attributes.hashID] = root[value.name];
        }else if(!value.attributes.cyclic){
            // ITS AN [OBJECT]
            root[value.name] = DJSNode.Revive(value, false);
            //console.log("Caching", value.attributes.hashID);
            DJSHelper.ReviveCache[value.attributes.hashID] = root[value.name];
        }
    });

    // [SEPARATE ITERATION] TO MAKE SURE ALL POSSIBLE
    // [CYCLIC] REFERENCES ARE CACHED PROPERLY
    xmlNode.children.forEach(function (value) {
        // Each children is an [DJSNode]
        // [Array]s are stored as [DJSNode] with an positive Array attribute
        // So is value

        if(value.attributes.cyclic){
            // ITS AND [CYCLIC] REFERENCE
            root[value.name] = DJSHelper.ReviveCache[value.attributes.hashID];
        }
    });

    if(isRoot){
        DJSHelper.ReviveCache = null; //Garbage Collect
    }
    return root;
};

DecycledJSON = {};
DecycledJSON.stringify = function (obj) {
    return JSON.stringify(new DJSNode("root", obj));
};
DecycledJSON.parse = function (json, replacerObject) {
    // use the replacerObject to get the null values
    return DJSNode.Revive(JSON.parse(json));
};
DJS = DecycledJSON;

Contoh Penggunaan 1:

var obj = {
    id:201,
    box: {
        owner: null,
        key: 'storm'
    },
    lines:[
        'item1',
        23
    ]
};

console.log(obj); // ORIGINAL

// SERIALIZE AND THEN PARSE
var jsonObj = DJS.stringify(obj);
console.log(DJS.parse(jsonObj));

Contoh Penggunaan 2:

// PERSON OBJECT

function Person() {
    this.name = null;
    this.child = null;
    this.dad = null;
    this.mom = null;
}
var Dad = new Person();
Dad.name = 'John';
var Mom = new Person();
Mom.name = 'Sarah';
var Child = new Person();
Child.name = 'Kiddo';

Dad.child = Mom.child = Child;
Child.dad = Dad;
Child.mom = Mom;

console.log(Child); // ORIGINAL

// SERIALIZE AND THEN PARSE
var jsonChild = DJS.stringify(Child);
console.log(DJS.parse(jsonChild));

0

Coba ini:

var obj = {
    a: "foo",
    b: obj
};

var circular_replacer = (value) => {
    var seen = [];
    if (value != null && typeof value == "object") {
        if (seen.indexOf(value) >= 0) return;
        seen.push(value);
    }
    return value;
};

obj = circular_replacer(obj);

Bukankah seharusnya ada, seperti, beberapa baris kode lagi setelah seen.push(value)= -D? Sukafor (var key in value) {value[key] = circular_replacer(value[key]);}
Klesun

Jawaban khusus kode tidak disarankan. Silakan klik edit dan tambahkan beberapa kata yang meringkas bagaimana kode Anda menjawab pertanyaan, atau mungkin menjelaskan bagaimana jawaban Anda berbeda dari jawaban / jawaban sebelumnya. Dari Ulasan
Nick

0

Dalam solusi saya, jika Anda mengalami siklus, itu tidak hanya mengatakan "siklus" (atau tidak sama sekali), ia mengatakan sesuatu seperti foo: lihat objek # 42 di atas, dan untuk melihat di mana foo menunjuk Anda dapat menggulir ke atas dan mencari untuk objek # 42 (setiap objek, ketika dimulai, mengatakan objek # xxx dengan beberapa bilangan bulat xxx)

Potongan:

(function(){
	"use strict";
	var ignore = [Boolean, Date, Number, RegExp, String];
	function primitive(item){
		if (typeof item === 'object'){
			if (item === null) { return true; }
			for (var i=0; i<ignore.length; i++){
				if (item instanceof ignore[i]) { return true; }
			}
			return false;
		} else {
			return true;
		}
	}
	function infant(value){
		return Array.isArray(value) ? [] : {};
	}
	JSON.decycleIntoForest = function decycleIntoForest(object, replacer) {
		if (typeof replacer !== 'function'){
			replacer = function(x){ return x; }
		}
		object = replacer(object);
		if (primitive(object)) return object;
		var objects = [object];
		var forest  = [infant(object)];
		var bucket  = new WeakMap(); // bucket = inverse of objects 
		bucket.set(object, 0);       // i.e., map object to index in array
		function addToBucket(obj){
			var result = objects.length;
			objects.push(obj);
			bucket.set(obj, result);
			return result;
		}
		function isInBucket(obj){
			return bucket.has(obj);
			// objects[bucket.get(obj)] === obj, iff true is returned
		}
		function processNode(source, target){
			Object.keys(source).forEach(function(key){
				var value = replacer(source[key]);
				if (primitive(value)){
					target[key] = {value: value};
				} else {
					var ptr;
					if (isInBucket(value)){
						ptr = bucket.get(value);
					} else {
						ptr = addToBucket(value);
						var newTree = infant(value);
						forest.push(newTree);
						processNode(value, newTree);
					}
					target[key] = {pointer: ptr};
				}
			});
		}
		processNode(object, forest[0]);
		return forest;
	};
})();
the = document.getElementById('the');
function consoleLog(toBeLogged){
  the.textContent = the.textContent + '\n' + toBeLogged;
}
function show(root){
	var cycleFree = JSON.decycleIntoForest(root);
	var shown = cycleFree.map(function(tree, idx){ return false; });
	var indentIncrement = 4;
	function showItem(nodeSlot, indent, label){
	  leadingSpaces = ' '.repeat(indent);
      leadingSpacesPlus = ' '.repeat(indent + indentIncrement);
	  if (shown[nodeSlot]){
	  consoleLog(leadingSpaces + label + ' ... see above (object #' + nodeSlot + ')');
        } else {
		  consoleLog(leadingSpaces + label + ' object#' + nodeSlot);
		  var tree = cycleFree[nodeSlot];
		  shown[nodeSlot] = true;
		  Object.keys(tree).forEach(function(key){
			var entry = tree[key];
			if ('value' in entry){
			  consoleLog(leadingSpacesPlus + key + ": " + entry.value);
                } else {
					if ('pointer' in entry){
						showItem(entry.pointer, indent+indentIncrement, key);
                    }
                }
			});
        }
    }
	showItem(0, 0, 'root');
}
cities4d = {
	Europe:{
		north:[
			{name:"Stockholm", population:1000000, temp:6},
			{name:"Helsinki", population:650000, temp:7.6}
		],
		south:[
			{name:"Madrid", population:3200000, temp:15},
			{name:"Rome", population:4300000, temp:15}
		]
	},
	America:{
		north:[
			{name:"San Francisco", population:900000, temp:14},
			{name:"Quebec", population:530000, temp:4}
		],
		south:[
			{name:"Rio de Janeiro", population:7500000, temp:24},
			{name:"Santiago", population:6300000, temp:14}
		]
	},
	Asia:{
		north:[
			{name:"Moscow", population:13200000, temp:6}
		]
	}
};
cities4d.Europe.north[0].alsoStartsWithS = cities4d.America.north[0];
cities4d.Europe.north[0].smaller = cities4d.Europe.north[1];
cities4d.Europe.south[1].sameLanguage = cities4d.America.south[1];
cities4d.Asia.ptrToRoot = cities4d;
show(cities4d)
<pre id="the"></pre>

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.