JSON과 같은 형식으로 원형 구조를 인쇄하려면 어떻게해야합니까?


680

JSON으로 변환하여 보내려는 큰 객체가 있습니다. 그러나 그것은 원형 구조를 가지고 있습니다. 순환 참조가 존재하는 것을 던지고 문자열로 지정할 수있는 것을 보내려고합니다. 어떻게합니까?

감사.

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

obj를 다음과 같이 문자열 화하고 싶습니다.

{"a":"foo"}

5
구문 분석하려는 순환 참조가있는 샘플 객체를 게시 할 수 있습니까?
TWickz

3
같은 ?
Alvin Wong


2
파티에 늦었지만 이것을 처리 하는 github 프로젝트가 있습니다.
Preston S

답변:


606

JSON.stringify맞춤 교체와 함께 사용하십시오 . 예를 들면 다음과 같습니다.

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

이 예에서 교체기는 100 % 정확하지 않습니다 ( "중복"의 정의에 따라 다름). 다음과 같은 경우 값이 삭제됩니다.

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

그러나 개념은 다음과 같습니다. 사용자 정의 대체기를 사용하고 구문 분석 된 오브젝트 값을 추적하십시오.

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 버그가 무엇인가요? 부정확 한 답변이 있으면 기꺼이 답변을 드리겠습니다.
Rob W

1
@CruzDiablo 직렬화 DOM은 일반적으로 의미가 없습니다. 그러나 목적에 맞는 의미있는 직렬화 방법을 생각할 수 있다면 DOM 객체에 직렬화 된 사용자 정의를 추가하려고 시도 할 수 있습니다. Node.prototype.toJSON = function() { return 'whatever you think that is right'; };(보다 일반적인 / 특정 항목을 원하면 프로토 타입 트리에서 아무것도 시도하십시오. HTMLDivElement는 HTMLElement 구현을 구현합니다. 요소가 구현하는 노드 구현의 EventTarget; 참고 :이 브라우저에 의존 할 수있다, 이전의 나무) 크롬 마찬가지입니다
롭 W

7
실제로 순환 구조가 아니더라도 두 번 포함 된 객체의 두 번째 모양을 건너 뛰기 때문에 잘못되었습니다. var a={id:1}; JSON.stringify([a,a]);
user2451227

3
@ user2451227 "이 예에서 대체 기는 100 % 정확하지 않습니다 ("중복 "의 정의에 따라 다름). 그러나 개념은 다음과 같습니다. 사용자 정의 대체기를 사용하고 구문 분석 된 오브젝트 값을 추적하십시오."
Rob W

4
여기서 GC 문제는 중복 될 수 있습니다. 이것이 단일 스크립트로 실행되면 스크립트가 즉시 종료됩니다. 이것이 구현을위한 함수 안에 캡슐화되어 cache있으면 접근 할 수없는 developer.mozilla.org/en-US/docs/Web/JavaScript/…
Trindaz

704

Node.js에서 util.inspect (object)를 사용할 수 있습니다 . 원형 링크를 "[Circular]"로 자동 대체합니다.


내장되어 있지만 (설치가 필요하지 않음) 가져와야합니다.

import * as util from 'util' // has no default export
import { inspect } from 'util' // or directly
// or 
var util = require('util')
사용하려면 간단히 전화하십시오.
console.log(util.inspect(myObject))

또한 검사 할 옵션 객체를 전달할 수 있습니다 (위 링크 참조).

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



아래 의견을 읽고 댓글을 달아주세요.


134
util은 내장 모듈이므로 설치할 필요가 없습니다.
Mitar

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

19
@Mitar 내장되어 있지만 여전히 모듈을로드해야합니다var util = require('util');
bodecker

14
나처럼 행동하지 마십시오. 단지 obj_str = util.inspect(thing) <s> garbage_str = JSON.stringify(util.inspect(thing))</ s>가
아닙니다

7
이것은 유형을 확인하는 것보다 훨씬 낫습니다. 왜 문자열 화가 이렇게 작동하지 않습니까? 순환 참조가 있다는 것을 안다면 왜 그것을 무시하도록 지시 할 수 없습니까 ???
Chris Peacock

141

아무도 아직 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());

확인 된 값은 배열이 아닌 세트에 저장해야합니다 ( 모든 요소에서 대체 프로그램이 호출 ) . 체인에서 JSON.stringify 각 요소 를 시도 하여 순환 참조로 이어질 필요는 없습니다 .

허용 된 답변과 마찬가지로이 솔루션은 원형 뿐만 아니라 반복되는 모든 값을 제거 합니다. 그러나 적어도 지수 복잡성은 없습니다.


깔끔하지만 ES2015에만 해당됩니다. IE 지원이 없습니다.
Martin Capodici

43
Yoda는 말합니다. "여전히 IE를 지원하는 경우 트랜스 필러를 사용해야합니다."
스페인 기차

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)undefined크롬으로 반환
roberto tomás

1
React + Typescript에서 작동합니다. 감사합니다
user3417479

76

그냥 해

npm i --save circular-json

그런 다음 js 파일에서

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

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

참고 :이 패키지와는 아무 관련이 없습니다. 그러나 나는 이것을 위해 사용합니다.

2020 업데이트

CircularJSON은 유지 관리 중이며 flatted 는 후속 버전입니다.


고마워요! 훌륭한 도서관, 많은 시간을 절약했습니다. 초소형 (단 1.4KB 축소)
Brian Haak

16
모듈을 사용하는 것보다 "직접"하는 것보다 더 정당성이 필요할 수 있다고 생각합니다. 그리고 JSON원칙 을 덮어 쓰는 것은 좋지 않습니다 .
Edwin

스텁 테스트에 사용할 오브젝트를 복사해야했습니다. 이 답변은 완벽했습니다. 객체를 복사 한 다음 재정의를 제거했습니다. 감사!!
Chris Sharp

1
저자에 따르면이 패키지는 더 이상 사용되지 않습니다. CircularJSON은 유지 관리 만하고, flatted는 후속 작업입니다. 링크 : github.com/WebReflection/flatted#flatted
Robert Molina

3
'flatted'(및 circle-json?) 패키지는 JSON.stringify () 기능을 복제하지 않습니다. 자체 JSON이 아닌 형식을 만듭니다. (예 : Flatted.stringify({blah: 1})결과 [{"blah":1}]) 누군가이 문제에 대해 문제를 제기하려고 시도한 것으로 나타 났으며, 작성자가 문제를 비난하고 문제를 댓글에 고정했습니다.
jameslol

48

나는 Trindaz의 솔루션을 더 좋아했지만 더 장황했지만 버그가있었습니다. 나는 그것을 좋아하는 사람을 위해 그들을 고쳤다.

또한 캐시 객체에 길이 제한을 추가했습니다.

인쇄중인 객체가 실제로 큰 경우-무한히 큰 의미-알고리즘을 제한하고 싶습니다.

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

이 줄에서 null 검사가 누락되었습니다 ")";
Isak

기꺼이 추가하겠습니다. 지금까지 문제가 발생했을 때 nullable이 무엇인지 알려주십시오.
guy mograbi

2
// 브라우저는 20K를 초과하여 인쇄하지 않습니다. 그러나 한도를 2k로 설정하십시오. 아마도 미래를위한 변화일까요?
Pochen

38

@RobW의 대답은 정확하지만 더 성능이 좋습니다! 해시 맵 / 세트를 사용하기 때문에 :

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

순환 참조와 깊이 중첩 된 객체의 경우, 시도 stringifyDeep => github.com/ORESoftware/safe-stringify
알렉산더 밀스

아마도 Set 구현은 배열과 indexOf를 후드에서 사용했을 가능성이 있지만 확인하지 않았습니다.
Alexander Mills

예를 들면 - - 이것은 심지어 서로 다른 값을 가진 자식 노드를 가진 부모 노드를 제거하고 {"a":{"b":{"a":"d"}}}, 심지어 빈 개체 {} 가진 노드 제거
Sandip Pingle

그 Sandip의 예를 보여줄 수 있습니까? gist.github.com 또는 이것 저것 작성
알렉산더 밀스

훌륭합니다 !!! 먼저 node.js 및 Fission ;-) 아래의 작업 솔루션 (위에서 2-3 기능 솔루션 만 확인)-라이브러리가 끊어졌습니다.
Tom

37

JSON.decycleDouglas Crockford가 구현 한 방법 도 있습니다 . 그의 cycle.js를 참조하십시오 . 이를 통해 거의 모든 표준 구조를 문자열화할 수 있습니다.

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

retrocycle메소드를 사용 하여 원본 객체를 다시 만들 수도 있습니다 . 따라서 객체를주기 위해 객체에서 사이클을 제거 할 필요가 없습니다.

그러나 이는 DOM 노드 (실제 사용 사례의 일반적인주기 원인) 에는 작동 하지 않습니다 . 예를 들어 다음과 같은 상황이 발생합니다.

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

그 문제를 해결하기 위해 포크를 만들었습니다 ( cycle.js fork 참조 ). 이것은 잘 작동합니다 :

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

내 포크 JSON.decycle(variable)에서 원본과 같이 작동하며 variableDOM 노드 / 요소 가 포함되어 있으면 예외가 발생합니다 .

사용 JSON.decycle(variable, true)하면 결과를 되돌릴 수 없다는 사실을 받아들입니다 (복귀 화는 DOM 노드를 다시 만들지 않습니다). DOM 요소는 어느 정도 식별 가능해야합니다. 예를 들어 div요소에 id가 있으면 string으로 대체됩니다 "div#id-of-the-element".


2
그의 코드와 귀하의 코드는 내가 사용할 때 "RangeError : 최대 호출 스택 크기를 초과했습니다."
jcollum

Fiddle에 코드를 제공하거나 Github에 문제를 추가하면 살펴볼 수 있습니다 : github.com/Eccenux/JSON-js/issues
Nux

이것이 내가 찾던 것입니다. JSON.decycle(a, true)기능을 비활성화하기 위해 매개 변수로 true를 전달하면 어떻게됩니까?
Rudra

@Rudra true는 stringifyNodes포크에서 옵션을 true로 만듭니다 . 예 div를 들어 id = "some-id"를 문자열로 덤프 합니다 : div#some-id. 일부 문제는 피할 수 있지만 완전히 순환 할 수는 없습니다.
Nux

이 NPM 패키지입니다 npmjs.com/package/json-js는 있지만 잠시 동안 업데이트되지 않았습니다
마이클 Freidgeim

23

@isaacs에서 json-stringify-safe 를 확인하는 것이 좋습니다 .NPM에서 사용됩니다.

BTW- Node.js를 사용하지 않는 경우 소스 코드관련 부분 에서 4-27 행을 복사하여 붙여 넣기 만하면 됩니다.

설치하기 위해서:

$ npm install json-stringify-safe --save

쓰다:

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

결과는 다음과 같습니다.

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

@Rob W에서 언급 한 바닐라 JSON.stringify 함수와 마찬가지로 "replacer"함수를 두 번째 인수로 전달하여 살균 동작을 사용자 정의 할 수도 있습니다 stringify(). 당신은 자신이 작업을 수행하는 방법에 대한 간단한 예를 필요로 발견하면, 난 그냥 사람이 읽을 수있는 문자열로 오류, regexps '에, 그리고 기능을 강제 변환 사용자 정의 대용품 썼다 여기를 .


13

모든 순환 참조의 키를 모르는 경우 미래의 Google 직원 이이 문제에 대한 해결책을 찾고 있다면 JSON.stringify 함수 주위에 래퍼를 사용하여 순환 참조를 배제 할 수 있습니다. https://gist.github.com/4653128 의 예제 스크립트를 참조하십시오 .

솔루션은 본질적으로 배열에서 이전에 인쇄 된 객체에 대한 참조를 유지하고 값을 반환하기 전에 대체 기능에서 확인합니다. 원형 참조를 배제하는 것보다 더 수 축적입니다. 객체를 두 번 인쇄하는 것을 배제하기 때문에 부작용 중 하나는 순환 참조를 피하는 것입니다.

래퍼 예 :

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
좋은 코드입니다. 그러나 어리석은 오류가 있습니다 . 명시 적으로 명시하지 않는 한 변환 될 수 있기 때문에 if(printedObjIndex)작성해야합니다 . if(printedObjIndex==false)index0false
guy mograbi

1
@guymograbi 무슨 의미 ===인가요? 0 == false이다 true, 0 === false입니다 false. ; ^) 그러나 나는 printedObjIndex거짓으로 초기화하지 않고 , undefined당신이 (잘, Trindaz의) 은유를 이상하게 혼합하지 않도록 확인할 수 있습니다 .
ruffin

@ruffin 좋은 캐치. 예, 분명히 바보 같은 실수를 잡기 위해 항상 단단한 평등과 jshint를 사용하십시오.
guy mograbi

4

교체기와 함께 JSON.stringify 메소드를 사용하십시오. 자세한 내용은이 설명서를 읽으십시오. 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));

교체 배열을 주기적 참조로 채우는 방법을 알아 봅니다. typeof 메소드를 사용하여 특성이 'object'(reference) 유형인지, 순환 참조를 확인하기위한 정확한 동등 검사 (===)인지 확인할 수 있습니다.


4
이것은 MSDN에서만 작동 할 수 있습니다 (MSDN은 Microsoft의 설명서이며 Microsoft는 IE를 생성한다는 점을 고려하면). Firefox / Chrome에서 jsfiddle.net/ppmaW 는 순환 참조 오류를 생성합니다. 참고 : 순환 참조를 작성 var obj = {foo:obj}하지 않습니다 . 대신, foo속성이 이전 값 obj( undefined이전에 정의되지 않은 경우로 인해 선언 됨 var obj)을 참조 하는 오브젝트를 작성합니다 .
Rob W

4

만약

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

결과

TypeError : 순환 객체 값

그런 다음 다음과 같이 인쇄 할 수 있습니다.

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

21
한 레벨 만 인쇄하기 때문에 어쩌면?
Alex Turpin

매우 간단합니다. 크롬 상자에서 즉시 작동했기 때문에 이것을 올렸습니다. 우수
사랑과 평화-Joe Codeswell

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

다음과 같이 평가됩니다.

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

기능으로 :

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

나는 이것이 오래된 질문이라는 것을 알고 있지만 제안한 다른 방식과 다르게 작동하는 smart-circular 라는 NPM 패키지를 제안하고 싶습니다 . 크고 깊은 객체를 사용하는 경우 특히 유용 합니다 .

일부 기능은 다음과 같습니다.

  • 문자열 [circular] 뿐만 아니라 첫 번째 발생으로 이어지는 경로로 객체 내부의 순환 참조 또는 단순히 반복 된 구조를 대체합니다 .

  • 너비 우선 검색에서 원형을 찾아 패키지는이 경로가 가능한 한 작게 유지되도록합니다. 이는 경로가 귀찮게 길고 따르기가 어려울 수있는 매우 크고 깊은 물체를 다룰 때 중요합니다. JSON.stringify는 DFS를 수행합니다);

  • 개체의 덜 중요한 부분을 단순화하거나 무시하는 데 편리한 개인화 된 교체를 허용합니다.

  • 마지막으로 경로는 참조 된 필드에 액세스하는 데 필요한 방식으로 정확하게 작성되므로 디버깅에 도움이됩니다.


3

JSON.stringify ()의 두 번째 인수를 사용하면 데이터 내에서 발생하는 모든 객체에서 보존해야하는 키 이름 배열을 지정할 수도 있습니다. 모든 사용 사례에 적용되는 것은 아니지만 훨씬 간단한 솔루션입니다.

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

참고 : 이상하게도 OP의 개체 정의는 최신 Chrome 또는 Firefox에서 순환 참조 오류를 발생시키지 않습니다. 이 때문에이 답변의 정의를 수정 오류가 발생.



이 답변을 받아 들여야합니다
조울증

2

JSON 작동 방식을 재정의하는 답변을 업데이트하려면 (권장하지 않지만 매우 간단 함) 사용하지 마십시오 circular-json(더 이상 사용되지 않음). 대신, 후계자를 사용하십시오.

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

@ user1541685의 위의 이전 답변에서 차용했지만 새로운 답변으로 대체되었습니다.

npm i --save flatted

그런 다음 js 파일에서

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

1

github 에서 Circular-json 라이브러리를 찾았고 문제에 잘 작동했습니다.

내가 찾은 좋은 기능들 :

  • 다중 플랫폼 사용을 지원하지만 지금까지 node.js로만 테스트했습니다.
  • API는 동일하므로 포함하고 JSON 대체로 사용하기 만하면됩니다.
  • 자체의 파싱 방법이 있으므로 '원형'직렬화 된 데이터를 다시 개체로 변환 할 수 있습니다.

2
이 라이브러리에서 오류가 발생하여 다른 라이브러리를 찾아야합니다. 오류 유형 오류 : toISOString은 Object. <anonymous> ( localhost : 8100 / build / polyfills.js : 1 : 3458 )의 String.toJSON (<anonymous>)에있는 함수가 아닙니다. Object의 JSON.stringify (<anonymous>)에 있습니다. stringifyRecursion [stringify] ( localhost : 8100 / build / main.js : 258450 : 15 )
Mark Ellul

1
@MarkEllul 나는 2015 년에 의견을 작성했으며 더 나은 대안을 볼 수 있다면 여기에 편집과 함께 게시 할 것입니다. 나는 여전히 일상 업무에서 종종 같은 문제를 겪고 있으며 적절하고 안전한 검사를 통해 재귀 방식으로 수동 기능을 선호합니다. 익숙하지 않은 경우 기능적 프로그래밍 관행을 확인하는 것이 좋습니다. 일반적 으로이 종류의 재귀 작업을 덜 까다 롭고 안정적으로 완화하고 있습니다.
JacopKane 2016 년

또한 "toISOString은 함수가 아닙니다"이벤트를 문자열 화하여 사이프러스 테스트에서 다시 보내려고합니다.
Devin G Rhode

1

이 문제를 다음과 같이 해결합니다.

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

이것은 나에게 거의 효과가 있었지만 클래스가 표현 된 것처럼 보였으므로 _class: ClassName { data: "here" }다음 규칙을 추가했다 .replace(/(\w+) {/g, '{ __ClassName__: "$1", '). 제 경우에는 http 요청 객체가 어떻게 보이는지 보려고했습니다.
redbmk

1

나는이 질문이 오래되었고 훌륭한 답변이 많이 있음을 알고 있지만 새로운 맛 (es5 +) 때문에이 답변을 게시합니다.


1

이것에 충분히 대답했지만 delete연산자를 사용하여 문자열 화하기 전에 문제의 속성을 명시 적으로 삭제할 수도 있습니다 .

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

연산자 삭제

따라서 순환 참조를 제거하기 위해 복잡한 논리를 작성하거나 유지 관리 할 필요가 없습니다.


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

다른 답변을 바탕으로 다음 코드로 끝납니다. 그것은 순환 참조, 사용자 정의 생성자가있는 객체와 꽤 잘 작동합니다.

주어진 객체에서 직렬화 할 때

  • 객체를 순회하면서 만나는 모든 객체를 캐시하고 각각에 고유 한 hashID를 할당합니다 (자동 증가 번호도 작동합니다)
  • 순환 참조가 발견되면 새 오브젝트에서 해당 필드를 순환으로 표시하고 원래 오브젝트의 hashID를 속성으로 저장하십시오.

Github에서 링크 - 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;

사용 예 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));

사용 예 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

이 시도:

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

seen.push(value)= -D 다음에 더 적은 코드 줄이 없어야합니까? 마찬가지로for (var key in value) {value[key] = circular_replacer(value[key]);}
Klesun

코드 전용 답변은 권장하지 않습니다. 편집을 클릭하고 코드에서 질문을 처리하는 방법을 요약 한 단어를 추가하거나 이전 답변 / 응답과 어떻게 다른지 설명하십시오. 검토에서
Nick

0

내 솔루션에서 사이클을 실행하면 "cycle"(또는 아무것도 없음)이라고 말하지 않고 foo와 같은 메시지가 나타납니다. 위의 객체 # 42를 참조하고 foo가 가리키는 위치를 스크롤하여 검색 할 수 있습니다. 객체 # 42의 경우 (각 객체는 시작할 때 정수 xxx가있는 객체 # xxx라고합니다)

단편:

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.