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 :(
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 :(
Câu trả lời:
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);
this
giá trị trong hàm mục tiêu, trong khi đó o
phải là tham số đầu tiên cho hàm. Mặc dù vậy, việc đặt nó thành this
(sẽ là traverse
hàm) hơi lạ một chút, nhưng dù sao nó cũng không giống như process
sử dụng this
tham chiếu. Nó cũng có thể là null.
/*jshint validthis: true */
ở trên func.apply(this,[i,o[i]]);
để tránh lỗi W040: Possible strict violation.
do sử dụngthis
traverse
hàm theo dõi độ sâu. Wenn gọi đệ quy thêm 1 vào mức hiện tại.
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.
function traverse(jsonObj) { if(jsonObj && typeof jsonObj == "object" ) { ...
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]);
}
}
}
much better
không?
!!o[i] && typeof o[i] == 'object'
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.
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
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)
})
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.
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 of
các vòng lặp như trong for(var a of b)
đó b
có thể lặp lại và a
là 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ể break
thoá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.keys
thay vì for in
chỉ 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.keys
và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);
}
Để 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.
//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);
}
Để 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.keys
và for...of
thay 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);
}
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);
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)
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"]
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>
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 )
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ể:
findFirst(rootObj, 'children', { id: 1 })
để tìm đối tượng đầu tiên với id === 1
findAll(rootObj, 'children', { id: 1 })
để tìm tất cả các đối tượng với id === 1
findAndDeleteFirst(rootObj, 'children', { id: 1 })
để xóa đối tượng phù hợp đầu tiênfindAndDeleteAll(rootObj, 'children', { id: 1 })
để xóa tất cả các đối tượng phù hợpreplacementObj
được sử dụng làm tham số cuối cùng trong hai phương thức cuối cùng:
findAndModifyFirst(rootObj, 'children', { id: 1 }, { id: 2, name: 'newObj'})
để thay đổi đầu tiên tìm thấy vật thể với id === 1
tới{ id: 2, name: 'newObj'}
findAndModifyAll(rootObj, 'children', { id: 1 }, { id: 2, name: 'newObj'})
để thay đổi tất cả các đối tượng với id === 1
tới{ id: 2, name: 'newObj'}