Làm cách nào tôi có thể in cấu trúc vòng tròn theo định dạng giống như JSON?


680

Tôi có một đối tượng lớn mà tôi muốn chuyển đổi sang JSON và gửi. Tuy nhiên, nó có cấu trúc tròn. Tôi muốn ném bất cứ thứ gì tham chiếu tròn tồn tại và gửi bất cứ thứ gì có thể được xâu chuỗi. Làm thế nào để làm điều đó?

Cảm ơn.

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

Tôi muốn xâu chuỗi obj vào:

{"a":"foo"}

5
Bạn có thể vui lòng gửi một đối tượng mẫu với một tham chiếu tròn mà bạn muốn phân tích không?
TWickz

3
một cái gì đó như thế này ?
Alvin Wong


2
Đi dự tiệc muộn nhưng có một dự án github để xử lý việc này.
Preston S

câu hỏi liên quan chặt chẽ: stackoverflow.com/questions/23117470/
Mạnh

Câu trả lời:


605

Sử dụng JSON.stringifyvới một thay thế tùy chỉnh. Ví dụ:

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

Công cụ thay thế trong ví dụ này không chính xác 100% (tùy theo định nghĩa của bạn về "trùng lặp"). Trong trường hợp sau, một giá trị bị loại bỏ:

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

Nhưng khái niệm này là: Sử dụng một trình thay thế tùy chỉnh và theo dõi các giá trị đối tượng được phân tích cú pháp.

Là một chức năng tiện ích được viết trong 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 Lỗi gì vậy? Tôi sẵn sàng sửa câu trả lời, nếu có bất kỳ sự thiếu chính xác nào trong đó.
Rob W

1
@CruzDiablo Tuần tự hóa DOM thường là vô nghĩa. Tuy nhiên, nếu bạn có thể nghĩ ra một phương thức tuần tự hóa có ý nghĩa cho mục đích của mình, thì bạn có thể thử thêm một tuần tự tùy chỉnh vào các đối tượng DOM: Node.prototype.toJSON = function() { return 'whatever you think that is right'; };(nếu bạn muốn bất cứ điều gì chung chung / cụ thể hơn, chỉ cần thử bất cứ điều gì trong cây nguyên mẫu: HTMLDivEuity thực hiện HTMLEuity Phần tử thực hiện Node thực hiện EventTarget; lưu ý: điều này có thể phụ thuộc vào trình duyệt, cây trước đó đúng với Chrome)
Rob W

7
Điều này là sai bởi vì nó sẽ bỏ qua sự xuất hiện thứ hai của các đối tượng được chứa hai lần, ngay cả khi không có cấu trúc thực sự tuần hoàn. var a={id:1}; JSON.stringify([a,a]);
user2451227

3
@ user2451227 "Trình thay thế trong ví dụ này không chính xác 100% (tùy theo định nghĩa của bạn về" trùng lặp "). Nhưng khái niệm này là: Sử dụng trình thay thế tùy chỉnh và theo dõi các giá trị đối tượng được phân tích cú pháp."
Rob W

4
Mối quan tâm của GC ở đây được cho là dư thừa. Nếu điều này được chạy dưới dạng một tập lệnh thì tập lệnh ngay lập tức chấm dứt. Nếu điều này được gói gọn trong một chức năng để thực hiện thì cachesẽ không thể truy cập được nhà phát
triển.mozilla.org / en

704

Trong Node.js, bạn có thể sử dụng produc.inspect (object) . Nó tự động thay thế các liên kết tròn bằng "[Thông tư]".


Mặc dù được tích hợp sẵn (không cần cài đặt) , bạn phải nhập nó

import * as util from 'util' // has no default export
import { inspect } from 'util' // or directly
// or 
var util = require('util')
Để sử dụng nó, chỉ cần gọi
console.log(util.inspect(myObject))

Cũng lưu ý rằng bạn có thể chuyển đối tượng tùy chọn để kiểm tra (xem liên kết ở trên)

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



Xin vui lòng, đọc và đưa danh tiếng cho những người bình luận bên dưới ...


134
tiện ích là một mô-đun tích hợp, bạn không phải cài đặt nó.
Mitar

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

19
@Mitar nó được tích hợp sẵn, nhưng bạn vẫn phải tải mô-đunvar util = require('util');
bodecker

14
Đừng là một obj_str = util.inspect(thing)garbage_str = JSON.stringify(util.inspect(thing))
kẻ ngu ngốc

7
Điều này là tốt hơn nhiều so với mucking xung quanh với các loại kiểm tra. Tại sao không thể xâu chuỗi chỉ hoạt động như thế này? Nếu nó biết có một tham chiếu tròn, tại sao nó không thể được yêu cầu bỏ qua nó ???
Chris Peacock

141

Tôi tự hỏi tại sao không ai đăng giải pháp thích hợp từ trang 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());

Các giá trị đã thấy nên được lưu trữ trong một tập hợp , không phải trong mảng (thay thế được gọi trên mọi phần tử ) và không cần phải thử JSON.stringify từng phần tử trong chuỗi dẫn đến tham chiếu vòng tròn.

Giống như trong câu trả lời được chấp nhận, giải pháp này sẽ loại bỏ tất cả các giá trị lặp lại , không chỉ các giá trị tròn. Nhưng ít nhất nó không có độ phức tạp theo cấp số nhân.


Gọn gàng, nhưng đây chỉ là ES2015. Không hỗ trợ IE.
Martin Capodici

43
Yoda nói: "Nếu vẫn hỗ trợ IE, thì nên sử dụng bộ chuyển mã."
Tàu Tây Ban Nha

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)trở lại undefinedtrong chrome
roberto tomás

1
Nó hoạt động trong React + Typecript. cảm ơn
user3417479

76

cứ làm đi

npm i --save circular-json

sau đó trong tập tin js của bạn

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

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

LƯU Ý: Tôi không có gì để làm với gói này. Nhưng tôi sử dụng nó cho việc này.

Cập nhật 2020

Xin lưu ý rằng Thông tư chỉ được bảo trì và được vỗ là sự kế thừa của nó.


Cảm ơn rất nhiều! Thư viện tuyệt vời, tiết kiệm hàng tấn thời gian. Siêu nhỏ (chỉ cần tối thiểu 1,4KB).
Brian Haak

16
Tôi nghĩ rằng bạn có thể yêu cầu một số biện minh cho việc sử dụng một mô-đun hơn là "chỉ cần làm". Và nó không tuyệt vời để ghi đè lên JSONnguyên tắc.
Edwin

Tôi cần phải sao chép một đối tượng để sử dụng để kiểm tra sơ khai. Câu trả lời này là hoàn hảo. Tôi đã sao chép đối tượng và sau đó loại bỏ ghi đè. Cảm ơn!!
Chris Sharp

1
Theo tác giả, gói này đã bị phản đối. Thông tư chỉ bảo trì, vỗ là kế của nó. Liên kết: github.com/WebReflection/flatted#flatted
Robert Molina

3
Coi chừng, gói 'flatted' (và circle-json?) Không sao chép chức năng JSON.opesify (). Nó tạo ra định dạng phi JSON của riêng nó. (ví dụ: Flatted.stringify({blah: 1})kết quả trong [{"blah":1}]) Tôi thấy ai đó đã cố gắng nêu ra một vấn đề về điều này, và tác giả đã mắng họ và khóa vấn đề này để bình luận.
jameslol

48

Tôi thực sự thích giải pháp của Trindaz - dài dòng hơn, tuy nhiên nó có một số lỗi. Tôi đã sửa chúng cho bất cứ ai thích nó.

Thêm vào đó, tôi đã thêm một giới hạn độ dài cho các đối tượng bộ nhớ cache của mình.

Nếu đối tượng tôi đang in thực sự lớn - ý tôi là vô cùng lớn - tôi muốn giới hạn thuật toán của mình.

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

Bạn đang thiếu kiểm tra null trên dòng này: return "(xem" + (!! value.constructor? Value.constructor.name.toLowerCase (): typeof (value)) + "với khóa" + printObjectKeys [printObj Index] + ")";
Isak

Tôi sẽ vui lòng thêm nó. chỉ cho tôi biết những gì là vô giá trị như tôi đã trải qua bất kỳ vấn đề cho đến nay.
anh chàng mograbi

2
// trình duyệt sẽ không in quá 20K - Nhưng bạn đặt giới hạn là 2k. Có lẽ thay đổi cho tương lai?
Pochen

38

Câu trả lời của @ RobW là chính xác, nhưng điều này hiệu quả hơn! Bởi vì nó sử dụng 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;
  });
};

Đối với các đối tượng được lồng sâu với các tham chiếu tròn, hãy thử StringifyDeep => github.com/ORESoftware/safe-opesify
Alexander Mills

Có thể việc triển khai Set chỉ sử dụng một mảng và indexOf dưới mui xe, nhưng tôi chưa xác nhận điều đó.
Alexander Mills

Điều này đang loại bỏ các nút cha có các nút con ngay cả với các giá trị khác nhau - ví dụ - {"a":{"b":{"a":"d"}}}và thậm chí loại bỏ các nút có đối tượng trống {}
Sandip Pingle

Bạn có thể đưa ra một ví dụ về Sandip đó? tạo một gist.github.com hoặc whatnot
Alexander Mills

Thông minh !!! Đầu tiên (từ trên cùng, nhưng chỉ kiểm tra 2-3 giải pháp chức năng) giải pháp làm việc ở đây dưới node.js và Fudge ;-) - thư viện bị treo.
Tom

37

Lưu ý rằng đó cũng là một JSON.decyclephương pháp được thực hiện bởi Douglas Crockford. Xem chu kỳ của anh ấy . Điều này cho phép bạn xâu chuỗi hầu hết mọi cấu trúc tiêu chuẩn:

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

Bạn cũng có thể tạo lại đối tượng ban đầu với retrocyclephương thức. Vì vậy, bạn không phải xóa chu kỳ khỏi các đối tượng để xâu chuỗi chúng.

Tuy nhiên, điều này sẽ không hoạt động đối với DOM Nodes (là nguyên nhân điển hình của chu kỳ trong các trường hợp sử dụng thực tế). Ví dụ, điều này sẽ ném:

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

Tôi đã tạo một ngã ba để giải quyết vấn đề đó (xem ngã ba chu kỳ của tôi ). Điều này sẽ hoạt động tốt:

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

Lưu ý rằng trong ngã ba của tôi JSON.decycle(variable)hoạt động như trong bản gốc và sẽ đưa ra một ngoại lệ khivariable chứa các nút / phần tử DOM.

Khi bạn sử dụng, JSON.decycle(variable, true)bạn chấp nhận thực tế là kết quả sẽ không thể đảo ngược (retro Motorcycle sẽ không tạo lại các nút DOM). Các yếu tố DOM nên được xác định ở một mức độ nào đó. Ví dụ: nếu một divphần tử có id thì nó sẽ được thay thế bằng một chuỗi "div#id-of-the-element".


2
Cả mã của anh ấy và của bạn đều cho tôi "RangeError: Kích thước ngăn xếp cuộc gọi tối đa vượt quá" khi tôi sử dụng chúng.
jcollum

Tôi có thể xem nếu bạn cung cấp mã của mình trên Fiddle hoặc thêm một vấn đề trên Github: github.com/Eccenux/JSON-js/issues
Nux

Đây là những gì tôi đang tìm kiếm. JSON.decycle(a, true)Điều gì xảy ra khi bạn chuyển true thành tham số cho hàm giải mã.
Rudra

@Rudra true làm cho stringifyNodestùy chọn đúng trong ngã ba. Điều này sẽ kết xuất ví dụ divvới id = "some-id" thành chuỗi : div#some-id. Bạn sẽ tránh được một số vấn đề, nhưng bạn sẽ không thể hoàn toàn quay lại theo chu kỳ.
Nux

Có gói npm npmjs.com/package/json-js , nhưng nó không được cập nhật trong một thời gian
Michael Freidgeim

23

Tôi khuyên bạn nên kiểm tra json-stringify-safe từ @ isaacs-- nó được sử dụng trong NPM.

BTW- nếu bạn không sử dụng Node.js, bạn có thể sao chép và dán các dòng 4-27 từ có liên quan của mã nguồn .

Để cài đặt:

$ npm install json-stringify-safe --save

Để sử dụng:

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

Sản lượng này:

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

Lưu ý rằng, giống như với hàm JSON.opesify của vanilla như @Rob W đã đề cập, bạn cũng có thể tùy chỉnh hành vi khử trùng bằng cách chuyển vào hàm "thay thế" làm đối số thứ hai stringify(). Nếu bạn thấy mình cần một ví dụ đơn giản về cách thực hiện việc này, tôi chỉ cần viết một trình thay thế tùy chỉnh để ép lỗi, biểu thức chính quy và các hàm thành các chuỗi có thể đọc được ở đây .


13

Đối với các nhân viên Google trong tương lai đang tìm kiếm giải pháp cho vấn đề này khi bạn không biết các khóa của tất cả các tham chiếu vòng tròn, bạn có thể sử dụng trình bao bọc xung quanh hàm JSON.opesify để loại trừ các tham chiếu vòng tròn. Xem tập lệnh mẫu tại https://gist.github.com/4653128 .

Về cơ bản, giải pháp thực hiện để giữ tham chiếu đến các đối tượng được in trước đó trong một mảng và kiểm tra xem trong hàm thay thế trước khi trả về giá trị. Điều này hạn chế hơn là chỉ loại trừ các tham chiếu vòng tròn, bởi vì nó cũng loại trừ việc in một đối tượng hai lần, một trong những tác động phụ là tránh các tham chiếu vòng tròn.

Ví dụ bao bọc:

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
Mã đẹp. Bạn có một lỗi ngớ ngẩn, bạn viết if(printedObjIndex)trong khi bạn nên viết if(printedObjIndex==false)bởi vì indexcũng có thể 0được dịch sang falsetrừ khi bạn nói rõ ràng khác.
anh chàng mograbi

1
@guymograbi Ý bạn là ===sao? 0 == falsetrue, 0 === falsefalse. ; ^) Nhưng tôi không muốn khởi tạo printedObjIndexthành sai, vì sau đó bạn có thể kiểm tra lại undefinedđể bạn (tốt, của Trindaz) không trộn lẫn các ẩn dụ một cách kỳ lạ.
ruffin

@ruffin bắt đẹp. vâng, rõ ràng, luôn luôn sử dụng bình đẳng cứng và jshint để bắt những sai lầm ngớ ngẩn như vậy.
anh chàng mograbi

4

Sử dụng phương thức JSON.opesify với trình thay thế. Đọc tài liệu này để biết thêm thông tin. http://msdn.microsoft.com/en-us/l Library / cc836459% 28v = vs.94% 29.aspx

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

var replacement = {"b":undefined};

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

Chỉ ra một cách để điền vào mảng thay thế bằng các tham chiếu theo chu kỳ. Bạn có thể sử dụng phương thức typeof để tìm xem một thuộc tính có thuộc loại 'đối tượng' (tham chiếu) và kiểm tra đẳng thức chính xác (===) để xác minh tham chiếu vòng tròn.


4
Điều này có thể chỉ hoạt động trong IE (xem xét thực tế rằng MSDN là tài liệu từ Microsoft và Microsoft tạo IE). Trong Firefox / Chrome, jsfiddle.net/ppmaW tạo ra lỗi tham chiếu vòng tròn. FYI: var obj = {foo:obj}không không tạo ra một tham chiếu vòng tròn. Thay vào đó, nó tạo ra một đối tượng có foothuộc tính tham chiếu đến giá trị trước đó obj( undefinednếu không được xác định trước đó, được khai báo vì var obj).
Rob W

4

Nếu

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

kết quả trong một

TypeError: giá trị đối tượng tuần hoàn

Sau đó, bạn có thể muốn in như thế này:

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

21
Có lẽ bởi vì nó chỉ in một cấp độ?
Alex Turpin

RẤT ĐƠN GIẢN tôi đã nâng cấp điều này bởi vì nó hoạt động với tôi ngay lập tức trong hộp bằng chrome. TUYỆT VỜI
Tình yêu và hòa bình - Joe Codwell

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

đánh giá:

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

với chức năng:

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

Tôi biết đây là một câu hỏi cũ, nhưng tôi muốn đề xuất gói NPM mà tôi đã tạo được gọi là thông minh , hoạt động khác với các cách khác được đề xuất. Nó đặc biệt hữu ích nếu bạn đang sử dụng các vật thể lớn và sâu .

Một số tính năng là:

  • Thay thế các tham chiếu vòng tròn hoặc đơn giản là các cấu trúc lặp lại bên trong đối tượng bằng đường dẫn đến lần xuất hiện đầu tiên của nó (không chỉ chuỗi [tròn] );

  • Bằng cách tìm kiếm các vòng tròn trong tìm kiếm đầu tiên, gói đảm bảo đường dẫn này càng nhỏ càng tốt, điều này rất quan trọng khi xử lý các vật thể rất lớn và sâu, trong đó các đường dẫn có thể kéo dài và khó theo dõi (thay thế tùy chỉnh trong JSON.opesify thực hiện một DFS);

  • Cho phép thay thế cá nhân hóa, tiện dụng để đơn giản hóa hoặc bỏ qua các phần ít quan trọng hơn của đối tượng;

  • Cuối cùng, các đường dẫn được viết chính xác theo cách cần thiết để truy cập vào trường được tham chiếu, có thể giúp bạn gỡ lỗi.


3

Đối số thứ hai cho JSON.opesify () cũng cho phép bạn chỉ định một mảng các tên khóa cần được bảo tồn khỏi mọi đối tượng mà nó gặp trong dữ liệu của bạn. Điều này có thể không hoạt động cho tất cả các trường hợp sử dụng, nhưng là một giải pháp đơn giản hơn nhiều.

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

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

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

Lưu ý: Thật kỳ lạ, định nghĩa đối tượng từ OP không gây ra lỗi tham chiếu vòng tròn trong Chrome hoặc Firefox mới nhất. Định nghĩa trong câu trả lời này đã được sửa đổi để nó đã ném ra một lỗi.



Câu trả lời này nên được chấp nhận
Trầm cảm hưng cảm

2

Để cập nhật câu trả lời về cách ghi đè cách thức hoạt động của JSON (có thể không được khuyến nghị, nhưng cực kỳ đơn giản), đừng sử dụng circular-json(nó không được dùng nữa). Thay vào đó, sử dụng kế, vỗ:

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

Mượn từ câu trả lời cũ ở trên từ @ user1541685, nhưng được thay thế bằng câu trả lời mới:

npm i --save flatted

sau đó trong tập tin js của bạn

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

1

Tôi tìm thấy thư viện json tròn trên github và nó hoạt động tốt cho vấn đề của tôi.

Một số tính năng tốt tôi thấy hữu ích:

  • Hỗ trợ sử dụng đa nền tảng nhưng cho đến nay tôi chỉ thử nghiệm nó với node.js.
  • API giống nhau, vì vậy tất cả những gì bạn cần làm là bao gồm và sử dụng nó như một sự thay thế JSON.
  • Nó có phương pháp phân tích cú pháp riêng để bạn có thể chuyển đổi dữ liệu tuần tự 'vòng tròn' trở lại đối tượng.

2
Thư viện này đã gây ra lỗi cho tôi vì vậy tôi phải tìm một cái khác. ERROR TypeError: toISOString không phải là một chức năng tại String.toJSON (<nặc danh>) tại Object. <Anonymous> ( localhost: 8100 / build / polyfills.js: 1: 3458 ) tại JSON.opesify (<nặc danh>) tại Object. stringifyRecursion [as stringify] ( localhost: 8100 / build / main.js: 258450: 15 )
Mark Ellul

1
@MarkEllul Tôi đã viết bình luận vào năm 2015 và nếu tôi thấy một sự thay thế tốt hơn tôi sẽ đăng nó ở đây với một chỉnh sửa. Thỉnh thoảng tôi vẫn gặp vấn đề tương tự trong công việc hàng ngày và tôi thường thích các chức năng thủ công của riêng mình theo cách đệ quy với kiểm tra thích hợp / an toàn. Tôi sẽ đề nghị kiểm tra các thực hành lập trình chức năng nếu bạn không quen, thông thường, việc nới lỏng loại hoạt động đệ quy này là ít phức tạp hơn và đáng tin cậy hơn.
JacopKane

Ngoài ra, nhận được "toISOString không phải là một chức năng" khi cố gắng xâu chuỗi một sự kiện và gửi lại nó trong một thử nghiệm cây bách
Devin G Rhode

1

Tôi giải quyết vấn đề này như thế này:

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

Điều này khá hiệu quả với tôi nhưng có vẻ như các lớp học được đại diện như thế _class: ClassName { data: "here" }, vì vậy tôi đã thêm quy tắc sau .replace(/(\w+) {/g, '{ __ClassName__: "$1", '). Trong trường hợp của tôi, tôi đã cố gắng để xem một đối tượng yêu cầu http trông như thế nào.
redbmk

1

Tôi biết câu hỏi này đã cũ và có rất nhiều câu trả lời hay nhưng tôi đăng câu trả lời này vì đó là hương vị mới (es5 +)


1

Mặc dù điều này đã được trả lời đầy đủ, bạn cũng có thể xóa rõ ràng thuộc tính được đề cập trước khi xâu chuỗi bằng cách sử dụng deletetoán tử.

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

xóa toán tử

điều này sẽ loại bỏ nhu cầu xây dựng hoặc duy trì logic phức tạp để loại bỏ các tham chiếu vòng tròn.


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

một giải pháp khác để giải quyết vấn đề này với các loại đối tượng này là sử dụng thư viện này

https://github.com/ericmuyser/opesy

nó đơn giản và bạn có thể trong một vài bước đơn giản giải quyết điều này.


0

Dựa trên các câu trả lời khác, tôi kết thúc với đoạn mã sau. Nó hoạt động khá tốt với các tham chiếu tròn, các đối tượng với các hàm tạo tùy chỉnh.

Từ đối tượng đã cho được nối tiếp,

  • Lưu trữ tất cả các đối tượng bạn gặp trong khi di chuyển ngang qua đối tượng và gán cho mỗi đối tượng một hashID duy nhất (một số tăng tự động cũng hoạt động)
  • Khi một tham chiếu vòng tròn được tìm thấy, đánh dấu trường đó trong đối tượng mới là hình tròn và lưu trữ hashID của đối tượng ban đầu làm thuộc tính.

Liên kết Github - DecyclJSON

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;

Cách sử dụng ví dụ 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));

Cách sử dụng ví dụ 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

Thử cái này:

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

Không nên có thêm vài dòng mã sau seen.push(value)= -D? Thíchfor (var key in value) {value[key] = circular_replacer(value[key]);}
Klesun

Câu trả lời chỉ có mã được khuyến khích. Vui lòng nhấp vào chỉnh sửa và thêm một số từ tóm tắt cách mã của bạn giải quyết câu hỏi hoặc có thể giải thích câu trả lời của bạn khác với câu trả lời / câu trả lời trước đó như thế nào. Từ đánh giá
Nick

0

Trong giải pháp của tôi, nếu bạn chạy vào một chu kỳ, nó không chỉ nói "chu kỳ" (hoặc không có gì), nó nói một cái gì đó như foo: xem đối tượng # 42 ở trên và để xem foo trỏ đến đâu bạn có thể cuộn lên và tìm kiếm đối với đối tượng # 42 (mỗi đối tượng, khi nó khởi động, cho biết đối tượng # xxx có một số nguyên xxx)

Đoạn trích:

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.