Я хотел бы просмотреть дерево объектов JSON, но не могу найти для этого никакой библиотеки. Это не кажется сложным, но похоже на изобретение колеса.
В XML так много учебных пособий, показывающих, как обходить дерево XML с помощью DOM :(
Я хотел бы просмотреть дерево объектов JSON, но не могу найти для этого никакой библиотеки. Это не кажется сложным, но похоже на изобретение колеса.
В XML так много учебных пособий, показывающих, как обходить дерево XML с помощью DOM :(
Ответы:
Если вы думаете, что 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);
this
значение в целевой функции, тогда o
как первым параметром этой функции должен быть. Установка его в this
(что было бы traverse
функцией) хоть и немного странная, но process
в this
любом случае она не использует ссылку. С таким же успехом это могло бы быть нулевым.
/*jshint validthis: true */
выше, func.apply(this,[i,o[i]]);
чтобы избежать ошибки, W040: Possible strict violation.
вызванной использованиемthis
traverse
функцию, которая отслеживает глубину. При вызове рекурсивно добавьте 1 к текущему уровню.
Объект 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(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
?
!!o[i] && typeof o[i] == 'object'
Существует новая библиотека для обхода данных JSON с помощью JavaScript, которая поддерживает множество различных вариантов использования.
https://npmjs.org/package/traverse
https://github.com/substack/js-traverse
Он работает со всеми видами объектов JavaScript. Он даже обнаруживает циклы.
Он также предоставляет путь к каждому узлу.
Зависит от того, что вы хотите сделать. Вот пример обхода дерева объектов 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)
})
EDIT : Все ниже примеры в этом ответе были отредактированы , чтобы включить новый переменный путь из выданного итератора , как на @ supersan по просьбе . Переменная path - это массив строк, где каждая строка в массиве представляет каждый ключ, к которому был получен доступ для получения итогового итерированного значения из исходного исходного объекта. Переменная пути может быть передана в функцию / метод get lodash . Или вы можете написать свою собственную версию get 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"]));
РЕДАКТИРОВАТЬ : Этот отредактированный ответ решает бесконечные обходы цикла.
Этот отредактированный ответ все еще предоставляет одно из дополнительных преимуществ моего исходного ответа, который позволяет вам использовать предоставленную функцию генератора для использования более чистого и простого итерируемого интерфейса (подумайте, используя for of
циклы, for(var a of b)
где b
итеративный a
элемент является элементом итерируемого элемента). ). Используя функцию генератора и будучи более простым API, он также помогает с повторным использованием кода, делая его таким образом, чтобы вам не приходилось повторять логику итерации везде, где вы хотите глубоко перебрать свойства объекта, и это также дает возможность break
выйти из цикл, если вы хотите остановить итерацию раньше.
Одна вещь, которую я заметил, что она не была рассмотрена и которой нет в моем первоначальном ответе, это то, что вы должны быть осторожны при обходе произвольных (то есть любого «случайного» набора) объектов, потому что объекты JavaScript могут быть самоссылочными. Это создает возможность иметь бесконечные циклические обходы. Однако немодифицированные данные 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 и в основном поддерживать более современные браузеры (проверьте совместимость с таблицей es6 в kangax ). Вы можете использовать генераторы 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.keys
и в for...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);
}
Я хотел использовать идеальное решение @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)
Мой сценарий:
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>
Вы можете получить все ключи / значения и сохранить иерархию с этим
// 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>);
}
Я создал библиотеку для обхода и редактирования глубоко вложенных объектов 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'
.
Третий параметр - это объект, который вы используете для поиска объекта / объектов, которые вы хотите найти / изменить / удалить. Например, если вы ищете объект с идентификатором, равным 1, то вы передадите { id: 1}
в качестве третьего параметра.
И вы можете:
findFirst(rootObj, 'children', { id: 1 })
найти первый объект с id === 1
findAll(rootObj, 'children', { id: 1 })
найти все объекты с id === 1
findAndDeleteFirst(rootObj, 'children', { id: 1 })
удалить первый соответствующий объектfindAndDeleteAll(rootObj, 'children', { id: 1 })
удалить все подходящие объектыreplacementObj
используется в качестве последнего параметра в двух последних методах:
findAndModifyFirst(rootObj, 'children', { id: 1 }, { id: 2, name: 'newObj'})
изменить первый найденный объект id === 1
на{ id: 2, name: 'newObj'}
findAndModifyAll(rootObj, 'children', { id: 1 }, { id: 2, name: 'newObj'})
изменить все объекты с id === 1
к{ id: 2, name: 'newObj'}