Jak wydrukować strukturę kołową w formacie podobnym do JSON?


680

Mam duży obiekt, który chcę przekonwertować na JSON i wysłać. Ma jednak kołową strukturę. Chcę podrzucić dowolne okrągłe odniesienia i wysłać wszystko, co można skreślić. Jak mogę to zrobić?

Dzięki.

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

Chcę zawęzić obiekt obj do:

{"a":"foo"}

5
Czy możesz zamieścić przykładowy obiekt z referencją cykliczną, którą chcesz przeanalizować?
TWickz

3
coś jak to ?
Alvin Wong,


2
Późno na imprezę, ale istnieje projekt github , który sobie z tym poradzi.
Preston S

Odpowiedzi:


606

Używaj JSON.stringifyz niestandardowym zamiennikiem. Na przykład:

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

Zamiennik w tym przykładzie nie jest w 100% poprawny (w zależności od definicji „duplikatu”). W następującym przypadku wartość jest odrzucana:

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

Ale koncepcja jest następująca: użyj niestandardowego zamiennika i śledź przeanalizowane wartości obiektu.

Jako funkcja narzędzia napisana w 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 Jaki jest błąd? Chętnie naprawię odpowiedź, jeśli są w niej jakieś nieścisłości.
Rob W

1
Serializacja DOM @CruzDiablo jest zwykle bez znaczenia. Jeśli jednak możesz wymyślić sensowną metodę serializacji dla swoich celów, możesz spróbować dodać niestandardową serializację do obiektów DOM: Node.prototype.toJSON = function() { return 'whatever you think that is right'; };(jeśli chcesz czegoś bardziej ogólnego / specyficznego, po prostu wypróbuj coś w drzewie prototypów: HTMLDivElement implementuje implementację HTMLElement Element implementuje Węzeł implementuje EventTarget; uwaga: może to być zależne od przeglądarki, poprzednie drzewo jest prawdziwe dla Chrome)
Rob W

7
jest to błędne, ponieważ pominie drugi wygląd obiektów, które są zawarte dwukrotnie, nawet jeśli nie są w naprawdę cyklicznej strukturze. var a={id:1}; JSON.stringify([a,a]);
user2451227,

3
@ user2451227 „Zamiennik w tym przykładzie nie jest w 100% poprawny (w zależności od definicji„ duplikatu ”). Ale koncepcja jest następująca: użyj niestandardowego zamiennika i śledź wartości parsowanych obiektów.”
Rob W

4
Obawy GC są tutaj prawdopodobnie zbędne. Jeśli jest uruchamiany jako pojedynczy skrypt, skrypt natychmiast się kończy. Jeśli jest to zawarte w funkcji do implementacji, to cachebędzie nieosiągalny developer.mozilla.org/en-US/docs/Web/JavaScript/…
Trindaz

704

W Node.js możesz użyć util.inspect (obiekt) . Automatycznie zamienia okrągłe linki na „[Circular]”.


Chociaż jest wbudowany (instalacja nie jest wymagana) , musisz go zaimportować

import * as util from 'util' // has no default export
import { inspect } from 'util' // or directly
// or 
var util = require('util')
Aby z niego skorzystać, wystarczy zadzwonić
console.log(util.inspect(myObject))

Pamiętaj również, że możesz przekazać obiekt opcji do sprawdzenia (patrz link powyżej)

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



Proszę przeczytać i wyrazić uznanie dla komentujących poniżej ...


134
util jest wbudowanym modułem, nie musisz go instalować.
Mitar

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

19
@Mitar jest wbudowany, ale nadal musisz załadować modułvar util = require('util');
bodecker

14
Nie bądź osłem jak ja, to jest po prostu obj_str = util.inspect(thing) NIE <s> garbage_str = JSON.stringify(util.inspect(thing))</s>
ThorSummoner

7
Jest to o wiele lepsza metoda niż sprawdzanie typów. Dlaczego nie można po prostu tak działać? Jeśli wie, że istnieje okólnik, dlaczego nie można go po prostu zignorować?
Chris Peacock

141

Zastanawiam się, dlaczego nikt jeszcze nie opublikował właściwego rozwiązania ze strony 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());

Widziane wartości powinny być przechowywane w zestawie , a nie w tablicy (zamiennik jest wywoływany na każdym elemencie ) i nie ma potrzeby próbowania JSON.stringify każdego elementu w łańcuchu, co prowadzi do odwołania cyklicznego.

Podobnie jak w zaakceptowanej odpowiedzi, to rozwiązanie usuwa wszystkie powtarzające się wartości , nie tylko okrągłe. Ale przynajmniej nie ma wykładniczej złożoności.


Zgrabne, ale to tylko ES2015. Brak obsługi IE.
Martin Capodici,

43
Yoda mówi: „Jeśli nadal wspiera IE, to użyj transpilatora, powinieneś”.
Pociąg hiszpański

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)powraca undefinedw chromie
roberto tomás

1
Działa w React + Typescript. dzięki
użytkownik3417479,

76

po prostu zrób

npm i --save circular-json

następnie w pliku js

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

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

UWAGA: Nie mam nic wspólnego z tym pakietem. Ale używam tego do tego.

Aktualizacja 2020

Uwaga: CircularJSON jest w trakcie konserwacji, a jego następca jest spłaszczony .


Wielkie dzięki! Świetna biblioteka, zaoszczędzone mnóstwo czasu. Bardzo mały (tylko 1,4 KB).
Brian Haak,

16
Myślę, że możesz potrzebować więcej uzasadnienia dla korzystania z modułu niż „po prostu zrób”. JSONZasadniczo nadpisywanie nie jest świetne .
Edwin

Musiałem skopiować obiekt, aby użyć go do testowania kodu pośredniczącego. Ta odpowiedź była idealna. Skopiowałem obiekt, a następnie usunąłem zastąpienie. Dzięki!!
Chris Sharp,

1
Według autora ten pakiet jest przestarzały. CircularJSON jest w trakcie konserwacji, a jego następca jest spłaszczony. Link: github.com/WebReflection/flatted#flatted
Robert Molina

3
Uwaga, pakiet „flatted” (i circular-json?) Nie replikuje funkcji JSON.stringify (). Tworzy własny format inny niż JSON. (np. Flatted.stringify({blah: 1})wyniki w [{"blah":1}]) Widzę, że ktoś próbował poruszyć problem na ten temat, a autor skrytykował go i zablokował problem w komentarzach.
jameslol,

48

Naprawdę podobało mi się rozwiązanie Trindaz - bardziej szczegółowe, jednak zawierało pewne błędy. Naprawiłem je dla każdego, kto to lubi.

Dodatkowo dodałem limit długości do moich obiektów pamięci podręcznej.

Jeśli obiekt, który drukuję, jest naprawdę duży - mam na myśli nieskończenie duży - chcę ograniczyć mój algorytm.

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

W tym wierszu brakuje kontroli zerowej: return ”(patrz„ + (!! value.constructor? Value.constructor.name.toLowerCase (): typeof (value)) + ”z kluczem” + printObjectKeys [drukowaneObjIndex] + „)”;
Isak

Z przyjemnością dodam to. po prostu daj mi znać, co jest zerowalne, ponieważ do tej pory napotkałem jakiekolwiek problemy.
guy mograbi

2
// przeglądarki nie będą drukować więcej niż 20 KB - ale ustawiasz limit jako 2 KB. Może zmiana na przyszłość?
Pochen

38

@ Odpowiedź RobW jest poprawna, ale jest bardziej wydajna! Ponieważ używa skrótu / zestawu:

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

W przypadku głęboko zagnieżdżonych obiektów z referencjami kołowymi spróbuj stringifyDeep => github.com/ORESoftware/safe-stringify
Alexander Mills

Możliwe, że implementacja Set po prostu używa tablicy i indexOf pod maską, ale tego nie potwierdziłem.
Alexander Mills,

To usuwa węzły nadrzędne mające węzły podrzędne nawet o różnych wartościach - np. - {"a":{"b":{"a":"d"}}}a nawet usuwa węzły z pustym obiektem {}
Sandip Pingle

Czy możesz pokazać przykład tego Sandipa? utwórz gist.github.com lub coś w tym stylu
Alexander Mills

Świetny !!! Najpierw (od góry, ale sprawdzono tylko 2-3 funkcje) działające rozwiązanie tutaj pod node.js i Fission ;-) - biblioteki powieszone.
Tom

37

Zauważ, że istnieje także JSON.decyclemetoda wdrożona przez Douglasa Crockforda. Zobacz jego cycle.js . Pozwala to na utworzenie łańcucha dowolnej niemal standardowej struktury:

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

Możesz także odtworzyć oryginalny obiekt za pomocą retrocyclemetody. Dzięki temu nie musisz usuwać cykli z obiektów, aby je skreślić.

Nie zadziała to jednak w przypadku węzłów DOM (które są typową przyczyną cykli w rzeczywistych przypadkach użycia). Na przykład spowoduje to rzucenie:

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

Zrobiłem rozwidlenie, aby rozwiązać ten problem (zobacz mój rozwidlenie cycle.js ). To powinno działać dobrze:

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

Zauważ, że w moim rozwidleniu JSON.decycle(variable)działa jak w oryginale i zgłosi wyjątek, gdyvariable zawierają węzły / elementy DOM.

Podczas korzystania JSON.decycle(variable, true)akceptujesz fakt, że wynik nie będzie odwracalny (retrocycle nie odtworzy węzłów DOM). Elementy DOM powinny być jednak do pewnego stopnia identyfikowalne. Na przykład, jeśli divelement ma identyfikator, zostanie zastąpiony łańcuchem "div#id-of-the-element".


2
Zarówno jego kod, jak i twój dają mi „RangeError: Przekroczono maksymalny rozmiar stosu wywołań”, kiedy ich używam.
jcollum

Mogę rzucić
Nux

Właśnie tego szukałem. JSON.decycle(a, true)co się stanie, gdy przekażesz true jako parametr w celu wyłączenia funkcji
Rudra

@Rudra true sprawia, że stringifyNodesopcja jest prawdziwa w rozwidleniu. Będzie to zrzucić na przykład divz id = "some-id" ciąg: div#some-id. Unikniesz pewnych problemów, ale nie będziesz w stanie w pełni przełączyć się na cykl.
Nux,

Jest pakiet npm npmjs.com/package/json-js , ale przez jakiś czas nie był aktualizowany
Michael Freidgeim

23

Polecam sprawdzenie json-stringify-safe from @ isaacs-- jest używany w NPM.

BTW - jeśli nie używasz Node.js, możesz po prostu skopiować i wkleić wiersze 4-27 z odpowiedniej części kodu źródłowego .

Żeby zainstalować:

$ npm install json-stringify-safe --save

Używać:

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

Daje to:

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

Zauważ, że podobnie jak w przypadku waniliowej funkcji JSON.stringify, jak wspomniano @Rob W, możesz również dostosować zachowanie odkażające, przekazując funkcję „zamiennika” jako drugi argument stringify(). Jeśli znajdziesz się potrzeby prosty przykład jak to zrobić, po prostu napisał niestandardową substytutu przekształcająca błędy, wyrażeń regularnych i funkcje w czytelnej dla człowieka ciągów tutaj .


13

Dla przyszłych pracowników szukających rozwiązania tego problemu, gdy nie znasz kluczy wszystkich odwołań cyklicznych, możesz użyć opakowania wokół funkcji JSON.stringify, aby wykluczyć odwołania cykliczne. Zobacz przykładowy skrypt na https://gist.github.com/4653128 .

Rozwiązanie zasadniczo sprowadza się do zachowania odniesienia do uprzednio wydrukowanych obiektów w tablicy i sprawdzenia tego w funkcji zastępującej przed zwróceniem wartości. Jest to bardziej restrykcyjne niż wykluczanie okrągłych odniesień, ponieważ wyklucza również dwukrotne drukowanie obiektu, którego jednym z bocznych skutków jest unikanie okrągłych odniesień.

Przykładowe opakowanie:

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
Niezły kod. Masz jednak głupi błąd, pisz if(printedObjIndex)podczas pisania, if(printedObjIndex==false)ponieważ indexmożna go również 0przetłumaczyć, falsechyba że wyraźnie postanowisz inaczej.
guy mograbi

1
@guymograbi Nie masz na myśli ===? 0 == falsejest true, 0 === falsejest false. ; ^) Ale wolałbym nie inicjować printedObjIndexna fałsz, ponieważ wtedy możesz się temu przeciwstawić undefined, abyś (no cóż, Trindaza) nie mieszał metafor w dziwny sposób.
ruffin

@ruffin nice catch. tak, oczywiście, zawsze używaj twardej równości i jshint, aby złapać takie głupie błędy.
facet mograbi

4

Użyj metody JSON.stringify z zamiennikiem. Przeczytaj tę dokumentację, aby uzyskać więcej informacji. 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));

Wymyśl sposób na zapełnienie tablicy zastępczej cyklicznymi odwołaniami. Możesz użyć metody typeof, aby sprawdzić, czy właściwość jest typu „obiekt” (referencja), a także dokładną kontrolę równości (===), aby zweryfikować referencję cykliczną.


4
Może to działać tylko w IE (biorąc pod uwagę fakt, że MSDN jest dokumentacją Microsoft, a Microsoft tworzy IE). W przeglądarce Firefox / Chrome jsfiddle.net/ppmaW generuje błąd odwołania cyklicznego. FYI: var obj = {foo:obj}nie nie tworzyć odwołanie cykliczne. Zamiast tego tworzy obiekt, którego fooatrybut odwołuje się do poprzedniej wartości obj( undefinedjeśli nie został wcześniej zdefiniowany, zadeklarowany z powodu var obj).
Rob W

4

Gdyby

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

skutkuje

TypeError: cykliczna wartość obiektu

Następnie możesz wydrukować w ten sposób:

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

21
Może dlatego, że drukuje tylko jeden poziom?
Alex Turpin,

BARDZO PROSTA Głosowałem za tym, ponieważ działało to dla mnie od razu po wyjęciu z pudełka w chrome. DOSKONAŁY
Miłość i pokój - Joe Codeswell

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

ocenia:

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

z funkcją:

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

Wiem, że to stare pytanie, ale chciałbym zasugerować utworzony przez siebie pakiet NPM o nazwie smart-circular , który działa inaczej niż inne proponowane sposoby. Jest to szczególnie przydatne, jeśli używasz dużych i głębokich obiektów .

Niektóre funkcje to:

  • Zastępowanie okrągłych odniesień lub po prostu powtarzanych struktur wewnątrz obiektu ścieżką prowadzącą do jego pierwszego wystąpienia (nie tylko ciąg [kołowy] );

  • Poszukując kolistości przy pierwszym wyszukiwaniu, pakiet zapewnia, że ​​ta ścieżka jest tak mała, jak to możliwe, co jest ważne, gdy mamy do czynienia z bardzo dużymi i głębokimi obiektami, gdzie ścieżki mogą stać się denerwująco długie i trudne do podążenia (niestandardowe zastąpienie w JSON.stringify robi DFS);

  • Umożliwia spersonalizowane zamiany, przydatne w celu uproszczenia lub zignorowania mniej ważnych części obiektu;

  • Wreszcie ścieżki są zapisywane dokładnie w sposób niezbędny do uzyskania dostępu do pola, do którego istnieje odwołanie, co może pomóc w debugowaniu.


3

Drugi argument JSON.stringify () również pozwala określić tablicę nazw kluczy, które powinny być zachowane od każdego obiektu, który napotka w danych. Może to nie działać we wszystkich przypadkach użycia, ale jest znacznie prostszym rozwiązaniem.

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

Uwaga: Co dziwne, definicja obiektu z OP nie rzuca okrągłego błędu odniesienia w najnowszym Chrome lub Firefox. Definicja zawarta w tej odpowiedzi została zmodyfikowana tak, aby nie rzucać się błąd.



Ta odpowiedź powinna zostać zaakceptowana
Depresja maniakalna

2

Aby zaktualizować odpowiedź przesłaniania sposobu działania JSON (prawdopodobnie nie jest to zalecane, ale bardzo proste), nie używaj circular-json(jest przestarzałe). Zamiast tego użyj następcy spłaszczonego:

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

Zapożyczono ze starej odpowiedzi powyżej od @ user1541685, ale zastąpiono nową:

npm i --save flatted

następnie w pliku js

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

1

Znalazłem bibliotekę circular-json na githubie i działała dobrze dla mojego problemu.

Kilka dobrych funkcji, które uznałem za przydatne:

  • Obsługuje użycie wielu platform, ale do tej pory testowałem go tylko z node.js.
  • Interfejs API jest taki sam, więc wszystko, co musisz zrobić, to dołączyć i używać go jako zamiennika JSON.
  • Ma własną metodę analizowania, dzięki czemu można przekształcić „okrągłe” serializowane dane z powrotem do obiektu.

2
Ta biblioteka rzuciła mi błąd, więc muszę poszukać innej. BŁĄD TypeError: toISOString nie jest funkcją w String.toJSON (<anonimowy>) w Object. <anonymous> ( localhost: 8100 / build / polyfills.js: 1: 3458 ) w JSON.stringify (<anonymous>) w Object. stringifyRecursion [as stringify] ( localhost: 8100 / build / main.js: 258450: 15 )
Mark Ellul

1
@ MarkEllul Napisałem komentarz w 2015 roku i jeśli zobaczę lepszą alternatywę, opublikuję go tutaj wraz z edycją. W dalszym ciągu od czasu do czasu mam tę samą kwestię w codziennej pracy i zwykle wolę własne funkcje ręczne w trybie rekurencyjnym z odpowiednią / bezpieczną kontrolą. Proponuję sprawdzić funkcjonalne praktyki programowania, jeśli nie jesteś zaznajomiony, zwykle ułatwia to tego rodzaju operacje rekurencyjne jako mniej skomplikowane i bardziej niezawodne.
JacopKane

Otrzymywanie również „toISOString nie jest funkcją” próbującą skreślić zdarzenie i wysłać je ponownie w teście cyprysowym
Devin G Rhode

1

Rozwiązuję ten problem w następujący sposób:

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

To prawie działało dla mnie, ale wygląda na to, że klasy były reprezentowane w taki sposób _class: ClassName { data: "here" }, więc dodałem następującą zasadę .replace(/(\w+) {/g, '{ __ClassName__: "$1", '). W moim przypadku próbowałem zobaczyć, jak wyglądał obiekt żądania HTTP.
redbmk

1

Wiem, że to pytanie jest stare i ma wiele świetnych odpowiedzi, ale zamieszczam tę odpowiedź z powodu jej nowego smaku (es5 +)


1

Chociaż odpowiedź na to pytanie jest wystarczająca, można również jawnie usunąć daną właściwość przed powiązaniem za pomocą deleteoperatora.

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

usuń operatora

usunie to potrzebę budowania lub utrzymywania złożonej logiki w celu usunięcia odwołań cyklicznych.


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

Na podstawie innych odpowiedzi otrzymuję następujący kod. Działa całkiem dobrze z referencjami kołowymi, obiektami z niestandardowymi konstruktorami.

Z danego obiektu do serializacji,

  • Przechowuj w pamięci podręcznej wszystkie obiekty, które napotkasz podczas przemierzania obiektu, i przypisz każdemu z nich unikalny hashID (działa także funkcja automatycznego zwiększania liczby)
  • Po znalezieniu odwołania cyklicznego zaznacz to pole w nowym obiekcie jako okrągłe i zapisz hashID oryginalnego obiektu jako atrybut.

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;

Przykład użycia 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));

Przykład użycia 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

Spróbuj tego:

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

Czy nie powinno być więcej wierszy kodu po seen.push(value)= -D? Jakfor (var key in value) {value[key] = circular_replacer(value[key]);}
Klesun,

Odpowiedzi zawierające tylko kod są odradzane. Kliknij edytuj i dodaj kilka słów podsumowujących, w jaki sposób Twój kod odpowiada na pytanie, lub może wyjaśnij, w jaki sposób Twoja odpowiedź różni się od poprzedniej odpowiedzi / odpowiedzi. Z recenzji
Nick

0

W moim rozwiązaniu, jeśli natrafisz na cykl, nie mówi on po prostu „cykl” (lub nic), mówi coś w rodzaju foo: patrz obiekt nr 42 powyżej i aby zobaczyć, gdzie foo wskazuje, możesz przewijać i wyszukiwać dla obiektu nr 42 (każdy obiekt, gdy się uruchamia, mówi obiekt nr xxx z jakąś liczbą całkowitą xxx)

Skrawek:

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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.