Пройдите по всем узлам дерева объектов JSON с помощью JavaScript - PullRequest
134 голосов
/ 06 апреля 2009

Я бы хотел просмотреть дерево объектов JSON, но не могу найти для этого никакой библиотеки. Это не кажется сложным, но кажется, что изобретать велосипед заново.

В XML так много учебных пособий, показывающих, как обходить дерево XML с помощью DOM: (

Ответы [ 15 ]

205 голосов
/ 06 апреля 2009

Если вы думаете, что 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);
57 голосов
/ 06 апреля 2009

Объект 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 для таких вещей, поскольку они значительно облегчают написание такого кода.

31 голосов
/ 24 января 2012
function traverse(o ) {
    for (i in o) {
        if (!!o[i] && typeof(o[i])=="object") {
            console.log(i, o[i])
            traverse(o[i] );
        }
    }
}
27 голосов
/ 18 июня 2011

Существует новая библиотека для обхода данных JSON с помощью JavaScript, которая поддерживает множество различных вариантов использования.

https://npmjs.org/package/traverse

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

Работает со всеми видами объектов JavaScript. Он даже обнаруживает циклы.

Он также предоставляет путь к каждому узлу.

12 голосов
/ 06 апреля 2009

Зависит от того, что вы хотите сделать. Вот пример обхода дерева объектов 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
7 голосов
/ 08 октября 2015

Если вы просматриваете фактическую строку 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)
})
5 голосов
/ 11 августа 2017

РЕДАКТИРОВАТЬ : Все ответы были отредактированы и теперь содержат новую переменную пути, полученную из итератора, согласно запросу @ supersan . Переменная path - это массив строк, где каждая строка в массиве представляет каждый ключ, к которому был получен доступ, чтобы получить результирующее повторяющееся значение из исходного исходного объекта. Переменная пути может быть передана в функцию / метод 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); }

4 голосов
/ 11 августа 2015

Я хотел использовать идеальное решение @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 голосов
/ 15 октября 2016

Большинство движков 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 голосов
/ 24 ноября 2017

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>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...