簡體   English   中英

使用 JavaScript 遍歷 JSON 對象樹的所有節點

[英]Traverse all the Nodes of a JSON Object Tree with JavaScript

我想遍歷 JSON 對象樹,但找不到任何庫。 這似乎並不困難,但感覺就像重新發明輪子。

在 XML 中,有很多教程展示了如何使用 DOM 遍歷 XML 樹 :(

如果你認為 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);

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 方法來處理這些事情,因為它們使編寫此類代碼變得更加容易。

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

有一個用於使用 JavaScript 遍歷 JSON 數據的新庫,該庫支持許多不同的用例。

https://npmjs.org/package/traverse

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

它適用於各種 JavaScript 對象。 它甚至可以檢測循環。

它也提供了每個節點的路徑。

原始簡化答案

如果您不介意放棄 IE 並且主要支持更多當前的瀏覽器(檢查kangax 的 es6 表以了解兼容性),則可以使用更新的方法來執行此操作。 您可以為此使用 es2015 生成器 我已經相應地更新了@TheHippo 的答案。 當然,如果你真的想要 IE 支持,你可以使用babel JavaScript 轉譯器。

 // Implementation of Traverse function* traverse(o, path=[]) { for (var i in o) { const itemPath = path.concat(i); yield [i,o[i],itemPath,o]; if (o[i] !== null && typeof(o[i])=="object") { //going one step down in the object tree!! yield* traverse(o[i], itemPath); } } } // Traverse usage: //that's all... no magic, no bloated framework for(var [key, value, path, parent] of traverse({ foo:"bar", arr:[1,2,3], subo: { foo2:"bar2" } })) { // do something here with each key and value console.log(key, value, path, parent); }

如果您只想要自己的可枚舉屬性(基本上是非原型鏈屬性),您可以將其更改為使用Object.keysfor...of循環進行迭代:

 function* traverse(o,path=[]) { for (var i of Object.keys(o)) { const itemPath = path.concat(i); yield [i,o[i],itemPath,o]; 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, parent] of traverse({ foo:"bar", arr:[1,2,3], subo: { foo2:"bar2" } })) { // do something here with each key and value console.log(key, value, path, parent); }

編輯:這個編輯過的答案解決了無限循環遍歷。

停止討厭的無限對象遍歷

這個編輯過的答案仍然提供了我原來的答案的附加好處之一,它允許您使用提供的生成器函數來使用更清晰和簡單的可迭代界面(想想使用for of循環,如for(var a of b) where b是可迭代的,而a是可迭代的元素)。 通過使用生成器函數以及更簡單的 api,它還有助於代碼重用,這樣您就不必在任何想要深入迭代對象屬性的地方重復迭代邏輯,並且還可以break如果您想更早地停止迭代,則循環的。

我注意到的一件事尚未解決並且不在我的原始答案中是您應該小心遍歷任意(即任何“隨機”組)對象,因為 JavaScript 對象可以自引用。 這創造了無限循環遍歷的機會。 然而,未修改的 JSON 數據不能自引用,因此如果您使用的是 JS 對象的這個特定子集,您不必擔心無限循環遍歷,您可以參考我的原始答案或其他答案。 這是一個非結束遍歷的例子(注意它不是一段可運行的代碼,否則它會使你的瀏覽器選項卡崩潰)。

同樣在我編輯的示例中的生成器對象中,我選擇使用Object.keys而不是for in其中僅迭代對象上的非原型鍵。 如果您想要包含原型密鑰,您可以自己更換它。 有關Object.keysfor in兩種實現,請參閱下面的原始答案部分。

更糟糕的是 - 這將無限循環自引用對象:

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

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

// this self-referential property assignment is the only real logical difference 
// from the above original example which ends up making this naive traversal 
// non-terminating (i.e. it makes it infinite loop)
o.o = o;

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

為了避免這種情況,你可以在閉包中添加一個集合,這樣當函數第一次被調用時,它開始構建它已經看到的對象的內存,並且一旦遇到一個已經看到的對象就不會繼續迭代。 下面的代碼片段就是這樣做的,因此可以處理無限循環的情況。

更好 - 這不會在自引用對象上無限循環:

 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, o]; if (o[i] !== null && typeof(o[i])=="object") { //going one step down in the object tree!! yield* innerTraversal(o[i], itemPath); } } } yield* innerTraversal(o); } //your object var o = { foo:"bar", arr:[1,2,3], subo: { foo2:"bar2" } }; /// this self-referential property assignment is the only real logical difference // from the above original example which makes more naive traversals // non-terminating (ie it makes it infinite loop) oo = o; console.log(o); //that's all... no magic, no bloated framework for(var [key, value, path, parent] of traverse(o)) { // do something here with each key and value console.log(key, value, path, parent); }


編輯:此答案中的所有上述示例均已編輯,以包含根據@supersan 的請求從迭代器產生的新路徑變量。 path 變量是一個字符串數組,其中數組中的每個字符串表示為從原始源對象獲取結果迭代值而訪問的每個鍵。 路徑變量可以輸入lodash 的 get function/method 或者您可以編寫自己的 lodash 版本,它只處理這樣的數組:

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

你也可以像這樣創建一個 set 函數:

 function set (object, path, value) { const obj = path.slice(0,-1).reduce((obj, pathItem) => obj ? obj[pathItem] : undefined, object) if(obj && obj[path[path.length - 1]]) { obj[path[path.length - 1]] = value; } return object; } const example = {a: [1,2,3], b: 4, c: { d: ["foo"] }}; // these paths exist on the object console.log(set(example, ["a", "0"], 2)); console.log(set(example, ["c", "d", "0"], "qux")); console.log(set(example, ["b"], 12)); // these paths do not exist on the object console.log(set(example, ["e", "f", "g"], false)); console.log(set(example, ["b", "f", "g"], null));

編輯 2020 年 9 月:我添加了一個父對象,以便更快地訪問上一個對象。 這可以讓您更快地構建反向遍歷器。 此外,您始終可以修改遍歷算法以進行廣度優先搜索而不是深度優先,這實際上可能更可預測,事實上這是帶有廣度優先搜索的 TypeScript 版本 由於這是一個 JavaScript 問題,我將把 JS 版本放在這里:

 var TraverseFilter; (function (TraverseFilter) { /** prevents the children from being iterated. */ TraverseFilter["reject"] = "reject"; })(TraverseFilter || (TraverseFilter = {})); function* traverse(o) { const memory = new Set(); function* innerTraversal(root) { const queue = []; queue.push([root, []]); while (queue.length > 0) { const [o, path] = queue.shift(); if (memory.has(o)) { // we've seen this object before don't iterate it continue; } // add the new object to our memory. memory.add(o); for (var i of Object.keys(o)) { const item = o[i]; const itemPath = path.concat([i]); const filter = yield [i, item, itemPath, o]; if (filter === TraverseFilter.reject) continue; if (item !== null && typeof item === "object") { //going one step down in the object tree!! queue.push([item, itemPath]); } } } } yield* innerTraversal(o); } //your object var o = { foo: "bar", arr: [1, 2, 3], subo: { foo2: "bar2" } }; /// this self-referential property assignment is the only real logical difference // from the above original example which makes more naive traversals // non-terminating (ie it makes it infinite loop) oo = o; //that's all... no magic, no bloated framework for (const [key, value, path, parent] of traverse(o)) { // do something here with each key and value console.log(key, value, path, parent); }

取決於你想做什么。 下面是一個遍歷 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

如果您要遍歷實際的 JSON字符串,則可以使用 reviver 函數。

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

我想在匿名函數中使用@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);

大多數 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)

我創建了庫來遍歷和編輯深層嵌套的 JS 對象。 在此處查看 API: https : //github.com/dominik791

您還可以使用演示應用程序以交互方式使用庫: https : //dominik791.github.io/obj-traverse-demo/

用法示例:您應該始終擁有根對象,它是每個方法的第一個參數:

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

第二個參數始終是包含嵌套對象的屬性的名稱。 在上述情況下,它將是'children'

第三個參數是一個對象,用於查找要查找/修改/刪除的對象。 例如,如果您要查找 id 等於 1 的對象,那么您將傳遞{ id: 1}作為第三個參數。

你可以:

  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用作最后兩個方法中的最后一個參數:

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

我的腳本:

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

 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>

我們將對象掃描用於許多數據處理任務。 一旦你把頭環繞在它周圍,它就很強大。 這是您如何進行基本遍歷

 // const objectScan = require('object-scan'); const obj = { foo: 'bar', arr: [1, 2, 3], subo: { foo2: 'bar2' } }; objectScan(['**'], { reverse: false, filterFn: ({ key, value }) => { console.log(key, value); } })(obj); // => [ 'foo' ] bar // => [ 'arr', 0 ] 1 // => [ 'arr', 1 ] 2 // => [ 'arr', 2 ] 3 // => [ 'arr' ] [ 1, 2, 3 ] // => [ 'subo', 'foo2' ] bar2 // => [ 'subo' ] { foo2: 'bar2' }
 .as-console-wrapper {max-height: 100% !important; top: 0}
 <script src="https://bundle.run/object-scan@13.8.0"></script>

免責聲明:我是對象掃描的作者

這會將所有節點讀取到地圖。

 function readJsonFile() { let jsonString = getValueById("testDataContent"); let jsonObj = JSON.parse(jsonString); let jsonElements = []; jsonElements = traverse(jsonObj, jsonElements); console.log(jsonElements) } function traverse(jsonObj, jsonElements) { if (jsonObj !== null && typeof jsonObj == "object") { Object.entries(jsonObj).forEach(([key, value]) => { if (typeof value == "object") { var obj = []; let map = new Map(); map.set(key, traverse(value, obj)) jsonElements.push(map); } else { var obj = []; obj.key = key; obj.value = value; jsonElements.push(obj); } }); } else { } return jsonElements; }

對我來說最好的解決方案如下:

簡單,不使用任何框架

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

您可以使用此獲取所有鍵/值並保留層次結構

// 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 )的修改

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM