Di chuyển tất cả các nút của cây đối tượng JSON bằng JavaScript


148

Tôi muốn duyệt qua cây đối tượng JSON, nhưng không thể tìm thấy bất kỳ thư viện nào cho điều đó. Nó không có vẻ khó khăn nhưng nó cảm thấy như phát minh lại bánh xe.

Trong XML có rất nhiều hướng dẫn chỉ ra cách đi ngang qua cây XML với DOM :(


1
Đã tạo iterator IIFE github.com/eltomjan/ETEhomeTools/blob/master/HTM_HTA/, nó đã được xác định trước (cơ bản) DepthFirst & BreadthFirst tiếp theo và khả năng di chuyển bên trong cấu trúc JSON mà không cần đệ quy.
Tom

Câu trả lời:


222

Nếu bạn nghĩ jQuery là một thứ quá mức cần thiết cho một nhiệm vụ nguyên thủy như vậy, bạn có thể làm một cái gì đó như thế:

//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
Tại sao fund.apply (này, ...)? Không phải là func.apply (o, ...)?
Craig Celeste

4
@ParchedSquid Không. Nếu bạn xem các tài liệu API để áp dụng () tham số đầu tiên là thisgiá trị trong hàm mục tiêu, trong khi đó ophải là tham số đầu tiên cho hàm. Mặc dù vậy, việc đặt nó thành this(sẽ là traversehàm) hơi lạ một chút, nhưng dù sao nó cũng không giống như processsử dụng thistham chiếu. Nó cũng có thể là null.
Thor84no

1
Đối với jshint ở chế độ nghiêm ngặt mặc dù bạn có thể cần thêm /*jshint validthis: true */ở trên func.apply(this,[i,o[i]]);để tránh lỗi W040: Possible strict violation.do sử dụngthis
Jasdeep Khalsa

4
@jasdeepkhalsa: Đúng vậy. Nhưng tại thời điểm viết câu trả lời, jshint thậm chí không được bắt đầu như một dự án trong một năm rưỡi.
TheHippo

1
@Vishal bạn có thể thêm 3 tham số cho traversehàm theo dõi độ sâu. Wenn gọi đệ quy thêm 1 vào mức hiện tại.
TheHippo

75

Một đối tượng JSON đơn giản là một đối tượng Javascript. Đó thực sự là những gì JSON là viết tắt của: Ký hiệu đối tượng JavaScript. Vì vậy, bạn đi ngang qua một đối tượng JSON, tuy nhiên, bạn thường chọn "duyệt" một đối tượng Javascript nói chung.

Trong ES2017 bạn sẽ làm:

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

Bạn luôn có thể viết một hàm để đệ quy xuống đối tượng:

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

Đây phải là một điểm khởi đầu tốt. Tôi đặc biệt khuyên bạn nên sử dụng các phương thức javascript hiện đại cho những thứ như vậy, vì chúng làm cho việc viết mã như vậy dễ dàng hơn nhiều.


9
Tránh di chuyển ngang (v) trong đó v == null, bởi vì (typeof null == "object") === true. function traverse(jsonObj) { if(jsonObj && typeof jsonObj == "object" ) { ...
Marcelo Amorim

4
Tôi ghét âm thanh pedantic, nhưng tôi nghĩ rằng đã có rất nhiều nhầm lẫn về điều này, vì vậy để cho rõ ràng tôi nói như sau. Các đối tượng JSON và JavaScript không giống nhau. JSON dựa trên định dạng của các đối tượng JavaScript, nhưng JSON chỉ là ký hiệu ; nó là một chuỗi các ký tự đại diện cho một đối tượng. Tất cả JSON có thể được "phân tích cú pháp" thành một đối tượng JS, nhưng không phải tất cả các đối tượng JS đều có thể được "xâu chuỗi" thành JSON. Ví dụ, các đối tượng JS tự tham chiếu không thể được xâu chuỗi.
Giăng

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
Bạn có thể giải thích tại sao much betterkhông?
Mất trí nhớ

3
Nếu phương thức này có nghĩa là làm bất cứ điều gì khác ngoài log, bạn nên kiểm tra null, null vẫn là một đối tượng.
wi1

3
@ wi1 Đồng ý với bạn, có thể kiểm tra!!o[i] && typeof o[i] == 'object'
pilau 2/2/2015

32

Có một thư viện mới để duyệt dữ liệu JSON bằng JavaScript hỗ trợ nhiều trường hợp sử dụng khác nhau.

https://npmjs.org/package/traverse

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

Nó hoạt động với tất cả các loại đối tượng JavaScript. Nó thậm chí còn phát hiện chu kỳ.

Nó cũng cung cấp đường dẫn của mỗi nút.


1
js-traverse dường như cũng có sẵn thông qua npm trong node.js.
Ville

Đúng. Nó chỉ được gọi là đi qua đó. Và họ có một trang web đáng yêu! Cập nhật câu trả lời của tôi để bao gồm nó.
Benjamin Atkin

15

Phụ thuộc vào những gì bạn muốn làm. Đây là một ví dụ về việc duyệt qua cây đối tượng JavaScript, in các khóa và giá trị khi nó đi:

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

Nếu bạn đang duyệt qua một chuỗi JSON thực thì bạn có thể sử dụng hàm 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)
})

Khi đi qua một đối tượng:

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

EDIT : Tất cả các ví dụ dưới đây trong câu trả lời này đã được chỉnh sửa để bao gồm một biến đường dẫn mới được tạo ra từ trình lặp theo yêu cầu của mỗi @ supersan . Biến đường dẫn là một chuỗi các chuỗi trong đó mỗi chuỗi trong mảng đại diện cho mỗi khóa được truy cập để lấy giá trị lặp từ kết quả của đối tượng nguồn ban đầu. Biến đường dẫn có thể được đưa vào hàm get / phương thức của lodash . Hoặc bạn có thể viết phiên bản lodash của riêng bạn, chỉ xử lý các mảng như vậy:

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

EDIT : Câu trả lời được chỉnh sửa này giải quyết các đường vòng lặp vô hạn.

Ngừng truy cập đối tượng vô hạn pky

Câu trả lời được chỉnh sửa này vẫn cung cấp một trong những lợi ích bổ sung của câu trả lời ban đầu của tôi, cho phép bạn sử dụng chức năng tạo được cung cấp để sử dụng giao diện lặp đơn giản và gọn gàng hơn (nghĩ rằng sử dụng for ofcác vòng lặp như trong for(var a of b)đó bcó thể lặp lại và alà một yếu tố của iterable ). Bằng cách sử dụng hàm tạo cùng với một api đơn giản hơn, nó cũng giúp tái sử dụng mã bằng cách làm cho nó không phải lặp lại logic lặp ở mọi nơi bạn muốn lặp lại sâu trên các thuộc tính của đối tượng và nó cũng có thể breakthoát ra khỏi vòng lặp nếu bạn muốn dừng lặp lại sớm hơn.

Một điều mà tôi nhận thấy rằng chưa được giải quyết và đó không phải là câu trả lời ban đầu của tôi là bạn nên cẩn thận di chuyển ngang qua các đối tượng "ngẫu nhiên", bởi vì các đối tượng JavaScript có thể tự tham chiếu. Điều này tạo ra cơ hội để có các đường vòng lặp vô hạn. Tuy nhiên, dữ liệu JSON chưa được sửa đổi không thể tự tham chiếu, vì vậy nếu bạn đang sử dụng tập hợp con đặc biệt này của các đối tượng JS, bạn không phải lo lắng về các giao dịch lặp vô hạn và bạn có thể tham khảo câu trả lời ban đầu của tôi hoặc các câu trả lời khác. Dưới đây là một ví dụ về một giao dịch không kết thúc (lưu ý rằng nó không phải là một đoạn mã có thể chạy được, bởi vì nếu không nó sẽ làm sập tab trình duyệt của bạn).

Ngoài ra, trong đối tượng trình tạo trong ví dụ đã chỉnh sửa của tôi, tôi đã chọn sử dụng Object.keysthay vì for inchỉ lặp lại các khóa không phải nguyên mẫu trên đối tượng. Bạn có thể tự trao đổi cái này nếu bạn muốn có các khóa nguyên mẫu đi kèm. Xem phần câu trả lời ban đầu của tôi dưới đây cho cả hai triển khai với Object.keysfor in .

Tệ hơn - Điều này sẽ lặp vô hạn trên các đối tượng tự tham chiếu:

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

Để tự cứu mình khỏi điều này, bạn có thể thêm một tập hợp trong một bao đóng, để khi hàm được gọi lần đầu tiên, nó bắt đầu xây dựng bộ nhớ của các đối tượng mà nó đã thấy và không tiếp tục lặp lại khi nó bắt gặp một đối tượng đã nhìn thấy. Đoạn mã dưới đây thực hiện điều đó và do đó xử lý các trường hợp lặp vô hạn.

Tốt hơn - Đây sẽ không phải là vòng lặp vô hạn trên các đối tượng tự tham chiếu:

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


Câu trả lời gốc

Để biết cách mới hơn để làm điều đó nếu bạn không bỏ rơi IE và chủ yếu hỗ trợ các trình duyệt hiện tại hơn (kiểm tra bảng es6 của kangax để biết tính tương thích). Bạn có thể sử dụng máy phát điện es2015 cho việc này. Tôi đã cập nhật câu trả lời của @ TheHippo tương ứng. Tất nhiên, nếu bạn thực sự muốn hỗ trợ IE, bạn có thể sử dụng trình chuyển mã JavaScript babel .

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

Nếu bạn chỉ muốn sở hữu các thuộc tính vô số (về cơ bản là các thuộc tính chuỗi không phải nguyên mẫu), bạn có thể thay đổi nó thành lặp lại bằng cách sử dụng Object.keysfor...ofthay vào đó là một vòng lặp:

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


Câu trả lời chính xác! Có thể trả về các đường dẫn như abc, abcd, v.v. cho mỗi khóa đang được duyệt không?
supersan

1
@supersan bạn có thể xem đoạn mã được cập nhật của tôi. Tôi đã thêm một biến đường dẫn cho mỗi một chuỗi đó là một chuỗi các chuỗi. Các chuỗi trong mảng đại diện cho mỗi khóa được truy cập để lấy giá trị lặp lại từ đối tượng nguồn ban đầu.
John

4

Tôi muốn sử dụng giải pháp hoàn hảo của @TheHippo trong một hàm ẩn danh, mà không sử dụng các hàm kích hoạt và quy trình. Sau đây làm việc cho tôi, chia sẻ cho các lập trình viên mới làm quen như tôi.

(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

Hầu hết các công cụ Javascript không tối ưu hóa đệ quy đuôi (điều này có thể không thành vấn đề nếu JSON của bạn không được lồng sâu), nhưng tôi thường nhầm lẫn về mặt thận trọng và thay vào đó, lặp đi lặp lại

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

Tập lệnh của tôi:

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 đầu vào:

[
    {
        "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": []
                    }
                ]
            }
       ]
   }
]

Chức năng gọi:

callback_func(inp_json);

Đầu ra theo nhu cầu của tôi:

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


làm cho nó để gửi mẫu ứng dụng enctype / json
seung

-1

Giải pháp tốt nhất cho tôi là như sau:

đơn giản và không sử dụng bất kỳ khuôn khổ nào

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

-1

Bạn có thể nhận được tất cả các khóa / giá trị và duy trì cấu trúc phân cấp với điều này

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

Đây là một sửa đổi trên ( 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

Tôi đã tạo thư viện để duyệt và chỉnh sửa các đối tượng JS lồng nhau sâu. Kiểm tra API tại đây: https://github.com/dominik791

Bạn cũng có thể chơi với thư viện tương tác bằng ứng dụng demo: https://dominik791.github.io/obj-traverse-demo/

Ví dụ về cách sử dụng: Bạn phải luôn có đối tượng gốc là tham số đầu tiên của mỗi phương thức:

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

Tham số thứ hai luôn là tên của thuộc tính chứa các đối tượng lồng nhau. Trong trường hợp trên nó sẽ được 'children'.

Tham số thứ ba là một đối tượng mà bạn sử dụng để tìm đối tượng / đối tượng mà bạn muốn tìm / sửa đổi / xóa. Ví dụ: nếu bạn đang tìm kiếm đối tượng có id bằng 1, thì bạn sẽ chuyển { id: 1}làm tham số thứ ba.

Và bạn có thể:

  1. findFirst(rootObj, 'children', { id: 1 }) để tìm đối tượng đầu tiên với id === 1
  2. findAll(rootObj, 'children', { id: 1 }) để tìm tất cả các đối tượng với id === 1
  3. findAndDeleteFirst(rootObj, 'children', { id: 1 }) để xóa đối tượng phù hợp đầu tiên
  4. findAndDeleteAll(rootObj, 'children', { id: 1 }) để xóa tất cả các đối tượng phù hợp

replacementObj được sử dụng làm tham số cuối cùng trong hai phương thức cuối cùng:

  1. findAndModifyFirst(rootObj, 'children', { id: 1 }, { id: 2, name: 'newObj'})để thay đổi đầu tiên tìm thấy vật thể với id === 1tới{ id: 2, name: 'newObj'}
  2. findAndModifyAll(rootObj, 'children', { id: 1 }, { id: 2, name: 'newObj'})để thay đổi tất cả các đối tượng với id === 1tới{ id: 2, name: 'newObj'}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.