172

他のノードへの参照である子ノードを含むオブジェクト(解析ツリー)があります。

を使用してこのオブジェクトをシリアル化したいのですJSON.stringify()が、

TypeError:循環オブジェクト値

私が言及した構造のため。

どうすればこれを回避できますか?他のノードへのこれらの参照がシリアル化されたオブジェクトで表されているかどうかは私には関係ありません。

一方、これらのプロパティを作成時にオブジェクトから削除するのは面倒なようで、パーサー(水仙)に変更を加えたくありません。

4

8 に答える 8

250

の2番目のパラメーターでstringifyある置換関数を使用して、すでにシリアル化されているオブジェクトを除外します。

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

于 2012-02-21T17:41:16.827 に答える
9

これは一種の代替回答ですが、多くの人がここに来るのは円形オブジェクトのデバッグであり、大量のコードを取得せずにそれを行うための優れた方法はありません。

あまり知られていない機能の1つJSON.stringify()ですconsole.table()。を呼び出すだけconsole.table(whatever);で、変数が表形式でコンソールに記録されるため、変数の内容を簡単に確認できます。

于 2020-07-10T17:26:01.800 に答える
5

循環参照を使用したデータ構造の例を次に示します。 toolshedCY

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

循環参照を保持したい場合(逆シリアル化するときに、それらを「ナック」するのではなく復元する)、2つの選択肢があります。ここで比較します。1つ目はDouglasCrockfordのcycle.jsで、2つ目は私のsiberiaパッケージです。どちらも、最初にオブジェクトを「逆循環」することによって機能します。つまり、「同じ情報を含む」別のオブジェクトを(循環参照なしで)構築します。

Crockford氏が最初に行きます:

JSON.decycle(makeToolshed())

JSON_decycleMakeToolshed

ご覧のとおり、JSONのネストされた構造は保持されていますが、新しいものがあります。それは、特別な$refプロパティを持つオブジェクトです。それがどのように機能するか見てみましょう。

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

ドル記号はルートを表します。これは「すでに見た」オブジェクトであり、その特別なプロパティの値(ここでは、文字列$ ["nut"] ["needs"])がどこにあるかを示しています。上記を最初に参照して.boltください。上記の2番目と2番目についても同様です。$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_makeToolshed

Siberiaは、ネストされた構造ではなく、「クラシック」JSONを模倣しようとはしません。オブジェクトグラフは「フラット」な方法で記述されます。オブジェクトグラフの各ノードはフラットツリー(整数のみの値を持つプレーンキー値ペアリスト)に変換されます。これは、.forest.インデックス0でルートオブジェクトを見つけ、より高いインデックスで他のノードを見つけます。オブジェクトグラフと(フォレストのツリーのキーの)負の値は、atoms配列を指します(types配列を介して入力されますが、ここでは入力の詳細をスキップします)。すべての終端ノードはアトムテーブルにあり、すべての非終端ノードはフォレストテーブルにあり、オブジェクトグラフにあるノードの数、つまり、をすぐに確認できます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

比較

後でセクションを追加します。

ノート

現在、パッケージをリファクタリングしています。中心的なアイデアとアルゴリズムは同じままですが、新しいバージョンは使いやすくなり、トップレベルのAPIは異なります。間もなくシベリアをアーカイブし、リファクタリングされたバージョンを提示します。これをオブジェクトグラフと呼びます。今月(2020年8月)に開催されますので、ご期待ください。

ああ、そして比較のための超短バージョン。「ポインタ」の場合、「すでに表示されているノードへのポインタ」(実際には、すでに表示されているかどうかに関係なく、すべてのノードへのポインタ)単なる整数であるため、整数と同じくらいのスペースが必要です。Crockford氏のバージョンでは、「ポインタ」を格納するために必要な量は、オブジェクトグラフのサイズによってのみ制限されます。それは、クロックフォード氏のバージョンの最悪の場合の複雑さを非常に恐ろしいものにします。クロックフォード氏は私たちに「別のバブルソート」をくれました。私はあなたをからかっていません。それは悪いです。信じられない場合は、テストがあります。パッケージのreadmeから開始してテストを見つけることができます(2020年8月の今月もbenchmark.jsに準拠するように変換されます)

于 2019-11-12T02:04:29.300 に答える
3

多くの節約とそれはサイクルオブジェクトがどこにあったかを示します。

<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__"]}]}
于 2013-04-22T18:05:27.210 に答える
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/

于 2014-03-28T21:31:48.607 に答える
1

文字列のように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のスクリプトを変換し、関数名をフランス語から英語に変更しました。

于 2015-03-29T15:44:45.997 に答える
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に減少します。

于 2014-11-08T11:35:11.870 に答える
0

nodejsモジュールserialijse は、サイクルまたはjavascriptクラスインスタンスを含むあらゆるタイプのJSONオブジェクトを処理するための優れた方法を提供します。

const { serialize, deserialize } = require("serialijse");


    var Mary = { name: "Mary", friends: [] };
    var Bob = { name: "Bob", friends: [] };

    Mary.friends.push(Bob);
    Bob.friends.push(Mary);

    var group = [ Mary, Bob];
    console.log(group);

    // testing serialization using  JSON.stringify/JSON.parse
    try {
        var jstr = JSON.stringify(group);
        var jo = JSON.parse(jstr);
        console.log(jo);

    } catch (err) {
        console.log(" JSON has failed to manage object with cyclic deps");
        console.log("  and has generated the following error message", err.message);
    }

    // now testing serialization using serialijse  serialize/deserialize
    var str = serialize(group);
    var so = deserialize(str);
    console.log(" However Serialijse knows to manage object with cyclic deps !");
    console.log(so);
    assert(so[0].friends[0] == so[1]); // Mary's friend is Bob

このシリアライザーはサポートします

  • オブジェクト定義のサイクル
  • クラスのインスタンスの再構築
  • 型付き配列、マップ、およびセットのサポート
  • シリアル化プロセス中にスキップするプロパティをフィルタリングする機能。
  • パフォーマンスのための型付き配列(Float32Arrayなど...)のバイナリエンコーディング。
于 2021-05-02T16:06:30.077 に答える