순환 객체 값을 포함하는 직렬화 객체


151

다른 노드에 대한 참조 인 하위 노드가 포함 된 객체 (구문 트리)가 있습니다.

내가 사용하는,이 객체를 직렬화하고 싶습니다 JSON.stringify(),하지만 난 얻을

TypeError : 순환 객체 값

내가 언급 한 구성 때문에

이 문제를 어떻게 해결할 수 있습니까? 다른 노드에 대한 이러한 참조가 직렬화 된 오브젝트에 표시되는지 여부는 중요하지 않습니다.

반면에 객체를 만들 때 객체에서 이러한 속성을 제거하는 것은 지루한 것처럼 보이고 파서 (수선화)를 변경하고 싶지 않습니다.


1
코드가 없으면 도와 드릴 수 없습니다. 직렬화에 사용하는 JS와 함께 객체 및 / 또는 JSON 출력의 관련 비트를 게시하십시오.
Bojangles

1
내부 참조 인 속성에 접두사를 추가 할 수 있습니까?
wheresrhys

@Loic Douglas Crockford의 cycle.js답변은 여기에 많은 도움이 될 것입니다. 많은 경우에 가장 적합한 솔루션이기 때문입니다. 답을 가장 먼저 참조하기 때문에 해당 답변을 게시하는 것이 적절 해 보입니다 (아래 의견 참조). 답변으로 직접 게시하고 싶지 않다면 결국 그렇게 할 것입니다.
Jeremy Banks


1
JSON이 더 똑똑하거나 더 쉽게 해결할 수 있기를 바랍니다. 솔루션은 간단한 (!) 디버깅 목적으로 너무 번거 롭습니다.
BluE

답변:


220

이미 직렬화 된 객체를 제외 시키 stringify려면 replacer 함수 의 두 번째 매개 변수를 사용하십시오 .

var seen = [];

JSON.stringify(obj, function(key, val) {
   if (val != null && typeof val == "object") {
        if (seen.indexOf(val) >= 0) {
            return;
        }
        seen.push(val);
    }
    return val;
});

http://jsfiddle.net/mH6cJ/38/

다른 주석에서 올바르게 지적했듯이이 코드는 "재귀 적"객체뿐만 아니라 모든 "본"객체를 제거합니다.

예를 들면 다음과 같습니다.

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

결과가 올바르지 않습니다. 구조가 이와 같으면 Crockford의 decycle 또는 재귀 참조를 null로 대체하는이 (간단한) 함수 를 사용할 수 있습니다 .

function decycle(obj, stack = []) {
    if (!obj || typeof obj !== 'object')
        return obj;
    
    if (stack.includes(obj))
        return null;

    let s = stack.concat([obj]);

    return Array.isArray(obj)
        ? obj.map(x => decycle(x, s))
        : Object.fromEntries(
            Object.entries(obj)
                .map(([k, v]) => [k, decycle(v, s)]));
}

//

let a = {b: [1, 2, 3]}
a.b.push(a);

console.log(JSON.stringify(decycle(a)))


3
아아 좋은! 고마워, 나는 이것을 시도 할 것이다. Douglas Crockford ( github.com/douglascrockford/JSON-js/blob/master/cycle.js )가 만든 솔루션을 찾았 지만 라이센스가 확실하지 않으면 설명하기 쉬운 솔루션이 완벽합니다!
Loic Duros 2012

3
@LoicDuros 라이센스는 "퍼블릭 도메인"입니다. 의미, 당신은 당신이 원하는대로 할 수 있습니다.
Ates Goral

1
이 코드는 순환 루프를 생성하고 사용에주의하며 잠재적으로 앱 충돌을 일으 킵니다. 올바른 세미콜론이 필요하고 이벤트 객체에는 사용할 수 없습니다!
Ol Sen

3
이것은 순환 참조 이상을 제거합니다. 단순히 두 번 이상 나타나는 것을 제거합니다. 이미 직렬화 된 객체가 새로운 객체의 "부모"가 아니라면 삭제해서는 안됩니다.
Gio

1
좋은 대답입니다! 나는 이것을 조금 수정하고 함수를 재귀 함수로 변경하여 자식 객체가 부모 객체가 복제되는 방식으로 복제되도록했습니다.
HoldOffHunger

2

순환 구조를 감지하고 해독 및 인코딩 할 수있는 GitHub Gist를 만들었습니다 : https://gist.github.com/Hoff97/9842228

변환하려면 JSONE.stringify / JSONE.parse를 사용하십시오. 또한 함수를 해독하고 인코딩합니다. 이것을 사용하지 않으려면 32-48 및 61-85 행을 제거하십시오.

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

여기에서 바이올린 예제를 찾을 수 있습니다.

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


2

이것은 대체 대답의 일종이지만 많은 사람들이 여기에 오는 것은 원형 객체를 디버깅하는 것이므로 많은 코드를 가져 오지 않고 실제로 수행 할 수있는 좋은 방법은 없습니다.

잘 알려지지 않은 기능 중 하나 JSON.stringify()입니다 console.table(). 간단히 호출 console.table(whatever);하면 콘솔에 변수를 표 형식으로 기록하므로 변수 내용을 쉽게 이해할 수 있습니다.


1

어디서 많이 보호기 그것은 보여 주기 객체가 있었다.

<script>
var jsonify=function(o){
    var seen=[];
    var jso=JSON.stringify(o, function(k,v){
        if (typeof v =='object') {
            if ( !seen.indexOf(v) ) { return '__cycle__'; }
            seen.push(v);
        } return v;
    });
    return jso;
};
var obj={
    g:{
        d:[2,5],
        j:2
    },
    e:10
};
obj.someloopshere = [
    obj.g,
    obj,
    { a: [ obj.e, obj ] }
];
console.log('jsonify=',jsonify(obj));
</script>

생산

jsonify = {"g":{"d":[2,5],"j":2},"e":10,"someloopshere":[{"d":[2,5],"j":2},"__cycle__",{"a":[10,"__cycle__"]}]}

그러나 누군가가 obj.b=this'잘못된 범위로 만들어진 매우 긴 종아리를 막는 방법을 알고 있다면 누군가가 객체를 만들면 여전히이 코드에 문제가 this있습니다.
Ol Sen

2
이것은이어야한다seen.indexOf(v) != -1

1

순환 객체를 직렬화하고 String과 같은 serializename 속성에 클래스를 저장하면 클래스를 복원 할 수있는 github 프로젝트를 너무 만듭니다.

var d={}
var a = {b:25,c:6,enfant:d};
d.papa=a;
var b = serializeObjet(a);
assert.equal(  b, "{0:{b:25,c:6,enfant:'tab[1]'},1:{papa:'tab[0]'}}" );
var retCaseDep = parseChaine(b)
assert.equal(  retCaseDep.b, 25 );
assert.equal(  retCaseDep.enfant.papa, retCaseDep );

https://github.com/bormat/serializeStringifyParseCyclicObject

편집 : NPM https://github.com/bormat/borto_circular_serialize에 대한 스크립트를 변환 했으며 함수 이름을 프랑스어에서 영어로 변경했습니다.


이 예는 요점에 맞지 않습니다. 요점에 오류가 있습니다.
Ernst Ernst

좋은 생각-그러나 한 번 준비하십시오 :-) npm으로 배포하면 타이핑조차 할 수 있습니다. 아마도 인기가있었습니다.
peterh-복 직원 모니카

1

순환 참조가있는 데이터 구조의 예는 다음과 같습니다. 공구 창고

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

순환 참조 ( KEEP)유지 하려면 ( "핵심"대신에 역 직렬화 할 때 복원) 두 가지 중에서 선택할 수 있습니다. 여기에서 비교하겠습니다. 첫 번째는 Douglas Crockford의 cycle.js 이고 두 번째는 내 시베리아 패키지입니다. 둘 다 먼저 객체를 "재순환"하여 (즉, 동일한 정보를 포함하는 다른 순환 참조없이) 다른 객체를 구성합니다.

Crockford 씨가 먼저갑니다 :

JSON.decycle(makeToolshed())

JSON_decycleMakeToolshed

보시다시피 JSON의 중첩 구조는 유지되지만 특별한 $ref속성을 가진 객체 인 새로운 것이 있습니다. 그것이 어떻게 작동하는지 봅시다.

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

달러 기호는 루트를 나타냅니다. .bolt있는 것은 $ref라고 우리에게 이야기 .bolt에 "이미 본"개체이며, 먼저 볼 경우 특별한 속성의 값이 (여기에서, 문자열 $ [ "너트"] [ "필요"]) 우리에게 ===위. 두 번째 $ref와 두 번째도 마찬가지입니다 ===.

복제가 작동하는지 확인 하기 위해 적절한 심도 평등 테스트 (즉, 이 질문에 대한deepGraphEqual 답변에서 Anders Kaseorg의 기능 )를 사용하십시오.

root = makeToolshed();
clone = JSON.retrocycle(JSON.decycle(root));
deepGraphEqual(root, clone) // true
serialized = JSON.stringify(JSON.decycle(root));
clone2 = JSON.retrocycle(JSON.parse(serialized));
deepGraphEqual(root, clone2); // true

시베리아

JSON.Siberia.forestify(makeToolshed())

JSON_Siberia_forestify_make 도구

시베리아는 "클래식"JSON을 모방하려고 시도하지 않고 중첩 구조를 사용하지 않습니다. 객체 그래프는 "평평한"방식으로 설명됩니다. 객체 그래프의 각 노드는 플랫 트리 (정수 전용 값을 갖는 일반 키 값 쌍 목록)로 바뀝니다 .forest.. 인덱스 0 의 항목 은 루트 객체를 찾고 높은 지수에서는 다른 노드를 찾습니다. 개체 그래프와 (포리스트 트리의 일부 키에 대한 음수 값)은 atoms배열을 가리 킵니다 (유형 배열을 통해 입력되지만 여기서는 입력 세부 정보를 건너 뜁니다). 모든 터미널 노드는 원자 테이블에 있고 모든 비 터미널 노드는 포리스트 테이블에 있으며 개체 그래프에있는 노드 수를 즉시 확인할 수 있습니다 forest.length. 작동하는지 테스트 해 봅시다.

root = makeToolshed();
clone = JSON.Siberia.unforestify(JSON.Siberia.forestify(root));
deepGraphEqual(root, clone); // true
serialized = JSON.Siberia.stringify(JSON.Siberia.forestify(root));
clone2 = JSON.Siberia.unforestify(JSON.Siberia.unstringify(serialized));
deepGraphEqual(root, clone2); // true

비교

나중에 섹션을 추가합니다.


0
function stringifyObject ( obj ) {
  if ( _.isArray( obj ) || !_.isObject( obj ) ) {
    return obj.toString()
  }
  var seen = [];
  return JSON.stringify(
    obj,
    function( key, val ) {
      if (val != null && typeof val == "object") {
        if ( seen.indexOf( val ) >= 0 )
          return
          seen.push( val )
          }
      return val
    }
  );
}

전제 조건이 누락되었습니다. 그렇지 않으면 배열 객체의 정수 값이 잘립니다. 즉 [[08.11.2014 12:30:13, 1095]] 1095가 095로 줄어 듭니다.


RefrenceError : 변수를 찾을 수 없음 : _
amit pandya

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