JavaScriptを使用してJSONオブジェクトツリーのすべてのノードをトラバースする


148

JSONオブジェクトツリーをトラバースしたいのですが、そのためのライブラリが見つかりません。難しくはないように見えますが、車輪を再発明するような気がします。

XMLには、DOMでXMLツリーをトラバースする方法を示すチュートリアルがたくさんあります:(


1
イテレーターIIFEを作成github.com/eltomjan/ETEhomeTools/blob/master/HTM_HTA/…事前定義された(基本)DepthFirst&BreadthFirst nextと、再帰なしでJSON構造内を移動する機能があります。
トム

回答:


222

jQueryがこのような原始的なタスクに対して一種のやり過ぎだと思う場合は、次のようにすることができます。

//your object
var o = { 
    foo:"bar",
    arr:[1,2,3],
    subo: {
        foo2:"bar2"
    }
};

//called with every property and its value
function process(key,value) {
    console.log(key + " : "+value);
}

function traverse(o,func) {
    for (var i in o) {
        func.apply(this,[i,o[i]]);  
        if (o[i] !== null && typeof(o[i])=="object") {
            //going one step down in the object tree!!
            traverse(o[i],func);
        }
    }
}

//that's all... no magic, no bloated framework
traverse(o,process);

2
なぜfund.apply(this、...)ですか?func.apply(o、...)にすべきではありませんか?
Craig Celeste 2014年

4
@ParchedSquidいいえ。app ()のAPIドキュメントを見ると、最初のパラメーターはthisターゲット関数の値ですが、関数oの最初のパラメーターである必要があります。thistraverse関数になる)に設定するのは少し奇妙ですが、とにかくリファレンスをprocess使用するようなものではありthisません。それも同様にnullでした。
Thor84no

1
厳密モードのjshintの場合、使用によって発生するエラーを回避するために/*jshint validthis: true */上記を追加する必要があるかもしれませんfunc.apply(this,[i,o[i]]);W040: Possible strict violation.this
Jasdeep Khalsa

4
@jasdeepkhalsa:その通りです。しかし、回答の執筆時点では、jshintはプロジェクトとして1年半も開始されていませんでした。
TheHippo、2015

1
@Vishal traverse深さを追跡する関数に3つのパラメーターを追加できます。Wenn呼び出しは、現在のレベルに再帰的に1を加えます。
TheHippo

75

JSONオブジェクトは単なるJavaScriptオブジェクトです。それが実際にJSONは、JavaScript Object Notationの略です。したがって、JSONオブジェクトをトラバースしますが、一般的にはJavaScriptオブジェクトを「トラバース」することを選択します。

ES2017では、次のようにします。

Object.entries(jsonObj).forEach(([key, value]) => {
    // do something with key and val
});

オブジェクトに再帰的に下降する関数をいつでも作成できます。

function traverse(jsonObj) {
    if( jsonObj !== null && typeof jsonObj == "object" ) {
        Object.entries(jsonObj).forEach(([key, value]) => {
            // key is either an array index or object key
            traverse(value);
        });
    }
    else {
        // jsonObj is a number or string
    }
}

これは良い出発点になるはずです。このようなものには、最新のjavascriptメソッドを使用することを強くお勧めします。これらのメソッドを使用すると、このようなコードの記述がはるかに簡単になります。


9
(typeof null == "object")=== trueであるため、v == nullのtraverse(v)は避けてください。 function traverse(jsonObj) { if(jsonObj && typeof jsonObj == "object" ) { ...
Marcelo Amorim

4
私は物静かに聞こえるのが嫌いですが、これについてはすでに多くの混乱があると思いますので、明確にするために次のように言います。JSONオブジェクトとJavaScriptオブジェクトは同じものではありません。JSONはJavaScriptオブジェクトのフォーマットに基づいていますが、JSONは単なる表記です。オブジェクトを表す文字列です。すべてのJSONをJSオブジェクトに「解析」できますが、すべてのJSオブジェクトをJSONに「文字列化」できるわけではありません。たとえば、自己参照JSオブジェクトは文字列化できません。
ジョン

36
function traverse(o) {
    for (var i in o) {
        if (!!o[i] && typeof(o[i])=="object") {
            console.log(i, o[i]);
            traverse(o[i]);
        } else {
            console.log(i, o[i]);
        }
    }
}

6
なぜか説明していただけますmuch betterか?
認知症2014

3
メソッドがログ以外のことを行うことを意図している場合は、nullを確認する必要がありますが、nullはまだオブジェクトです。
wi1 2014

3
@ wi1同意します。次の点を確認できます!!o[i] && typeof o[i] == 'object'
pilau

32

JavaScriptを使用してJSONデータをトラバースするための新しいライブラリーがあり、さまざまなユースケースをサポートしています。

https://npmjs.org/package/traverse

https://github.com/substack/js-traverse

すべての種類のJavaScriptオブジェクトで動作します。サイクルも検出します。

各ノードのパスも提供します。


1
js-traverseは、node.jsのnpmからも利用できるようです。
Ville

はい。トラバースと呼ばれているだけです。そして、彼らは素敵なウェブページを持っています!それを含めるために私の答えを更新します。
Benjamin Atkin、

15

あなたが何をしたいかに依存します。JavaScriptオブジェクトツリーをたどり、キーと値を表示する例を以下に示します。

function js_traverse(o) {
    var type = typeof o 
    if (type == "object") {
        for (var key in o) {
            print("key: ", key)
            js_traverse(o[key])
        }
    } else {
        print(o)
    }
}

js> foobar = {foo: "bar", baz: "quux", zot: [1, 2, 3, {some: "hash"}]}
[object Object]
js> js_traverse(foobar)                 
key:  foo
bar
key:  baz
quux
key:  zot
key:  0
1
key:  1
2
key:  2
3
key:  3
key:  some
hash

9

実際のJSON 文字列をトラバースする場合は、リバイバー関数を使用できます。

function traverse (json, callback) {
  JSON.parse(json, function (key, value) {
    if (key !== '') {
      callback.call(this, key, value)
    }
    return value
  })
}

traverse('{"a":{"b":{"c":{"d":1}},"e":{"f":2}}}', function (key, value) {
  console.log(arguments)
})

オブジェクトをトラバースする場合:

function traverse (obj, callback, trail) {
  trail = trail || []

  Object.keys(obj).forEach(function (key) {
    var value = obj[key]

    if (Object.getPrototypeOf(value) === Object.prototype) {
      traverse(value, callback, trail.concat(key))
    } else {
      callback.call(obj, key, value, trail)
    }
  })
}

traverse({a: {b: {c: {d: 1}}, e: {f: 2}}}, function (key, value, trail) {
  console.log(arguments)
})

8

編集:この回答の以下の例はすべて、@ supersanの要求に従ってイテレーターから生成された新しいパス変数を含むように編集されています。パス変数は文字列の配列であり、配列内の各文字列は、元のソースオブジェクトからの結果の反復値を取得するためにアクセスされた各キーを表します。パス変数は、lodashのget function / methodに供給することができます。または、次のように配列のみを処理する独自のバージョンのlodashのgetを作成することもできます。

function get (object, path) {
  return path.reduce((obj, pathItem) => obj ? obj[pathItem] : undefined, object);
}

const example = {a: [1,2,3], b: 4, c: { d: ["foo"] }};
// these paths exist on the object
console.log(get(example, ["a", "0"]));
console.log(get(example, ["c", "d", "0"]));
console.log(get(example, ["b"]));
// these paths do not exist on the object
console.log(get(example, ["e", "f", "g"]));
console.log(get(example, ["b", "f", "g"]));

編集:この編集された回答は、無限ループ走査を解決します。

厄介な無限オブジェクトトラバーサルの停止

この編集された答えは、私の元の答えの追加の利点の1つを提供します。これにより、提供されたジェネレーター関数を使用して、よりクリーンでシンプルな反復可能なインターフェースを使用できますfor ofループが、反復可能で反復可能の要素であるfor(var a of b)場合のようにループを使用すると考えてください))。あなたは、オブジェクトのプロパティに深く反復処理したいどこでも反復ロジックを繰り返す必要はありません、それはまたすることが可能となるので、それはまたそれを作ることにより、コードの再利用に役立ちますシンプルなAPIであることとともに、発電機能を使用することにより外ループを早く停止したい場合。babreak

JavaScriptオブジェクトは自己参照である可能性があるため、対処されておらず、私の元の答えにはないことの1つは、任意の(つまり、「ランダムな」セットの)オブジェクトを注意深くトラバースする必要があることです。これにより、無限ループ走査を行う機会が生まれます。ただし、変更されていないJSONデータは自己参照できないため、JSオブジェクトのこの特定のサブセットを使用している場合は、無限ループトラバーサルを心配する必要はなく、元の回答や他の回答を参照できます。次に、終了しない走査の例を示します(それ以外の場合はブラウザーのタブがクラッシュするため、実行可能なコードではないことに注意してください)。

また、編集した例のジェネレーターオブジェクトでは、オブジェクトの非プロトタイプキーのみを反復するObject.keys代わりに使用することを選択しfor inました。プロトタイプキーを含めたい場合は、これを自分で交換できます。両方の実装については、以下の私の元の答えのセクションを参照してくださいObject.keysし、for in

悪い-これは自己参照オブジェクトで無限ループになります:

//your object
var o = { 
    foo:"bar",
    arr:[1,2,3],
    subo: {
        foo2:"bar2"
    }
};

// this self-referential property assignment is the only edited line 
// from the below original example which makes the traversal 
// non-terminating (i.e. it makes it infinite loop)
o.o = o;

function* traverse(o, path=[]) {
    for (var i of Object.keys(o)) {
        const itemPath = path.concat(i);
        yield [i,o[i],itemPath]; 
        if (o[i] !== null && typeof(o[i])=="object") {
            //going one step down in the object tree!!
            yield* traverse(o[I], itemPath);
        }
    }
}

//that's all... no magic, no bloated framework
for(var [key, value, path] of traverse(o)) {
  // do something here with each key and value
  console.log(key, value, path);
}

これから身を守るために、クロージャー内にセットを追加できます。これにより、関数が最初に呼び出されたときに、それが見たオブジェクトのメモリの構築を開始し、すでに見たオブジェクトに出くわしたら繰り返しを継続しません。以下のコードスニペットはこれを実行し、無限ループのケースを処理します。

より良い-これは自己参照オブジェクトの無限ループにはなりません:

//your object
var o = { 
  foo:"bar",
  arr:[1,2,3],
  subo: {
    foo2:"bar2"
  }
};

// this self-referential property assignment is the only edited line 
// from the below original example which makes more naive traversals 
// non-terminating (i.e. it makes it infinite loop)
o.o = o;

function* traverse(o) {
  const memory = new Set();
  function * innerTraversal (o, path=[]) {
    if(memory.has(o)) {
      // we've seen this object before don't iterate it
      return;
    }
    // add the new object to our memory.
    memory.add(o);
    for (var i of Object.keys(o)) {
      const itemPath = path.concat(i);
      yield [i,o[i],itemPath]; 
      if (o[i] !== null && typeof(o[i])=="object") {
        //going one step down in the object tree!!
        yield* innerTraversal(o[i], itemPath);
      }
    }
  }
    
  yield* innerTraversal(o);
}
console.log(o);
//that's all... no magic, no bloated framework
for(var [key, value, path] of traverse(o)) {
  // do something here with each key and value
  console.log(key, value, path);
}


元の回答

IEのドロップを気にせず、主に最新のブラウザーをサポートしている場合の新しい方法については、互換性についてkangaxのes6テーブルを確認してください。これにはes2015 ジェネレーターを使用できます。私はそれに応じて@TheHippoの回答を更新しました。もちろん、IEサポートが本当に必要な場合は、babel JavaScriptトランスパイラーを使用できます。

//your object
var o = { 
    foo:"bar",
    arr:[1,2,3],
    subo: {
        foo2:"bar2"
    }
};

function* traverse(o, path=[]) {
    for (var i in o) {
        const itemPath = path.concat(i);
        yield [i,o[i],itemPath];
        if (o[i] !== null && typeof(o[i])=="object") {
            //going one step down in the object tree!!
            yield* traverse(o[i], itemPath);
        }
    }
}

//that's all... no magic, no bloated framework
for(var [key, value, path] of traverse(o)) {
  // do something here with each key and value
  console.log(key, value, path);
}

列挙可能なプロパティ(基本的に非プロトタイプチェーンプロパティ)のみが必要な場合はObject.keysfor...of代わりにとループを使用して反復するように変更できます。

//your object
var o = { 
    foo:"bar",
    arr:[1,2,3],
    subo: {
        foo2:"bar2"
    }
};

function* traverse(o,path=[]) {
    for (var i of Object.keys(o)) {
        const itemPath = path.concat(i);
        yield [i,o[i],itemPath];
        if (o[i] !== null && typeof(o[i])=="object") {
            //going one step down in the object tree!!
            yield* traverse(o[i],itemPath);
        }
    }
}

//that's all... no magic, no bloated framework
for(var [key, value, path] of traverse(o)) {
  // do something here with each key and value
  console.log(key, value, path);
}


正解です。トラバースされるキーごとにabc、abcdなどのパスを返すことはできますか?
supersan

1
@supersanあなたは私の更新されたコードスニペットを表示できます。文字列の配列であるそれぞれにパス変数を追加しました。配列内の文字列は、元のソースオブジェクトからの結果の反復値に到達するためにアクセスされた各キーを表します。
ジョン

4

@TheHippoの完全なソリューションを、プロセスおよびトリガー関数を使用せずに、匿名関数で使用したかったのです。以下は私のために働き、私のような初心者プログラマーのために共有しました。

(function traverse(o) {
    for (var i in o) {
        console.log('key : ' + i + ', value: ' + o[i]);

        if (o[i] !== null && typeof(o[i])=="object") {
            //going on step down in the object tree!!
            traverse(o[i]);
        }
    }
  })
  (json);

2

ほとんどのJavaScriptエンジンは末尾再帰を最適化しません(JSONが深くネストされていない場合、これは問題にならない可能性があります)が、通常は注意を怠って代わりに反復を行います。たとえば、

function traverse(o, fn) {
    const stack = [o]

    while (stack.length) {
        const obj = stack.shift()

        Object.keys(obj).forEach((key) => {
            fn(key, obj[key], obj)
            if (obj[key] instanceof Object) {
                stack.unshift(obj[key])
                return
            }
        })
    }
}

const o = {
    name: 'Max',
    legal: false,
    other: {
        name: 'Maxwell',
        nested: {
            legal: true
        }
    }
}

const fx = (key, value, obj) => console.log(key, value)
traverse(o, fx)

0

私のスクリプト:

op_needed = [];
callback_func = function(val) {
  var i, j, len;
  results = [];
  for (j = 0, len = val.length; j < len; j++) {
    i = val[j];
    if (i['children'].length !== 0) {
      call_func(i['children']);
    } else {
      op_needed.push(i['rel_path']);
    }
  }
  return op_needed;
};

入力JSON:

[
    {
        "id": null, 
        "name": "output",   
        "asset_type_assoc": [], 
        "rel_path": "output",
        "children": [
            {
                "id": null, 
                "name": "output",   
                "asset_type_assoc": [], 
                "rel_path": "output/f1",
                "children": [
                    {
                        "id": null, 
                        "name": "v#",
                        "asset_type_assoc": [], 
                        "rel_path": "output/f1/ver",
                        "children": []
                    }
                ]
            }
       ]
   }
]

関数呼び出し:

callback_func(inp_json);

私の必要に応じて出力:

["output/f1/ver"]

0

var test = {
    depth00: {
        depth10: 'string'
        , depth11: 11
        , depth12: {
            depth20:'string'
            , depth21:21
        }
        , depth13: [
            {
                depth22:'2201'
                , depth23:'2301'
            }
            , {
                depth22:'2202'
                , depth23:'2302'
            }
        ]
    }
    ,depth01: {
        depth10: 'string'
        , depth11: 11
        , depth12: {
            depth20:'string'
            , depth21:21
        }
        , depth13: [
            {
                depth22:'2201'
                , depth23:'2301'
            }
            , {
                depth22:'2202'
                , depth23:'2302'
            }
        ]
    }
    , depth02: 'string'
    , dpeth03: 3
};


function traverse(result, obj, preKey) {
    if(!obj) return [];
    if (typeof obj == 'object') {
        for(var key in obj) {
            traverse(result, obj[key], (preKey || '') + (preKey ? '[' +  key + ']' : key))
        }
    } else {
        result.push({
            key: (preKey || '')
            , val: obj
        });
    }
    return result;
}

document.getElementById('textarea').value = JSON.stringify(traverse([], test), null, 2);
<textarea style="width:100%;height:600px;" id="textarea"></textarea>


それがフォームを送信するためになさのenctype applicatioin / JSON

-1

私にとって最善の解決策は次のとおりです:

シンプルでフレームワークを使用しない

    var doSomethingForAll = function (arg) {
       if (arg != undefined && arg.length > 0) {
            arg.map(function (item) {
                  // do something for item
                  doSomethingForAll (item.subitem)
             });
        }
     }

-1

すべてのキー/値を取得し、これで階層を保持できます

// get keys of an object or array
function getkeys(z){
  var out=[]; 
  for(var i in z){out.push(i)};
  return out;
}

// print all inside an object
function allInternalObjs(data, name) {
  name = name || 'data';
  return getkeys(data).reduce(function(olist, k){
    var v = data[k];
    if(typeof v === 'object') { olist.push.apply(olist, allInternalObjs(v, name + '.' + k)); }
    else { olist.push(name + '.' + k + ' = ' + v); }
    return olist;
  }, []);
}

// run with this
allInternalObjs({'a':[{'b':'c'},{'d':{'e':5}}],'f':{'g':'h'}}, 'ob')

これは(https://stackoverflow.com/a/25063574/1484447)の変更です


-1
             var localdata = [{''}]// Your json array
              for (var j = 0; j < localdata.length; j++) 
               {$(localdata).each(function(index,item)
                {
                 $('#tbl').append('<tr><td>' + item.FirstName +'</td></tr>);
                 }

-1

深くネストされたJSオブジェクトをトラバースおよび編集するためのライブラリを作成しました。ここでAPIを確認してください:https : //github.com/dominik791

デモアプリを使用してインタラクティブにライブラリを再生することもできますhttps : //dominik791.github.io/obj-traverse-demo/

使用例:各メソッドの最初のパラメーターであるルートオブジェクトが常に必要です。

var rootObj = {
  name: 'rootObject',
  children: [
    {
      'name': 'child1',
       children: [ ... ]
    },
    {
       'name': 'child2',
       children: [ ... ]
    }
  ]
};

2番目のパラメーターは常に、ネストされたオブジェクトを保持するプロパティの名前です。上記の場合はそうなります'children'

3番目のパラメーターは、検索/変更/削除するオブジェクトを検索するために使用するオブジェクトです。たとえば、idが1のオブジェクトを探している場合は{ id: 1}、3番目のパラメータとして渡します。

そして、次のことができます:

  1. findFirst(rootObj, 'children', { id: 1 }) で最初のオブジェクトを見つける id === 1
  2. findAll(rootObj, 'children', { id: 1 }) すべてのオブジェクトを検索するには id === 1
  3. findAndDeleteFirst(rootObj, 'children', { id: 1 }) 最初に一致したオブジェクトを削除するには
  4. findAndDeleteAll(rootObj, 'children', { id: 1 }) 一致するすべてのオブジェクトを削除するには

replacementObj 最後の2つのメソッドの最後のパラメーターとして使用されます。

  1. findAndModifyFirst(rootObj, 'children', { id: 1 }, { id: 2, name: 'newObj'})変化に最初にオブジェクトを見つけid === 1{ id: 2, name: 'newObj'}
  2. findAndModifyAll(rootObj, 'children', { id: 1 }, { id: 2, name: 'newObj'})ですべてのオブジェクトを変更するid === 1{ id: 2, name: 'newObj'}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.