Пройдите по всем узлам дерева объектов JSON с помощью JavaScript

148

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

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

Пэтси Исса
источник
1
Сделал итератор IIFE github.com/eltomjan/ETEhomeTools/blob/master/HTM_HTA/… он имеет предопределенный (базовый) тип DepthFirst & BreadthFirst и возможность перемещаться внутри структуры JSON без рекурсии.
Том

Ответы:

222

Если вы думаете, что 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);
TheHippo
источник
2
Почему fund.apply (это, ...)? Разве это не должно быть func.apply (o, ...)?
Крейг Селесте
4
@ParchedSquid Нет. Если вы посмотрите на документы API для apply (), то первым параметром является thisзначение в целевой функции, тогда oкак первым параметром этой функции должен быть. Установка его в this(что было бы traverseфункцией) хоть и немного странная, но processв thisлюбом случае она не использует ссылку. С таким же успехом это могло бы быть нулевым.
Thor84no
1
Для jshint в строгом режиме, хотя вам может потребоваться добавить /*jshint validthis: true */выше, func.apply(this,[i,o[i]]);чтобы избежать ошибки, W040: Possible strict violation.вызванной использованиемthis
Jasdeep Khalsa
4
@jasdeepkhalsa: Это правда. Но на момент написания ответа jshint даже не начинался как проект в течение полутора лет.
TheHippo
1
@Vishal вы можете добавить 3 параметра в traverseфункцию, которая отслеживает глубину. При вызове рекурсивно добавьте 1 к текущему уровню.
TheHippo
75

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

Эли Кортрайт
источник
9
Избегайте traverse (v), где v == null, потому что (typeof null == "object") === true. function traverse(jsonObj) { if(jsonObj && typeof jsonObj == "object" ) { ...
Марсело Аморим
4
Я ненавижу звучать педантично, но я думаю, что в этом уже много путаницы, поэтому для ясности скажу следующее. Объекты JSON и JavaScript - это не одно и то же. JSON основан на форматировании объектов JavaScript, но JSON - это просто обозначение ; это строка символов, представляющих объект. Весь JSON может быть "проанализирован" в объект JS, но не все объекты JS могут быть "преобразованы в строку" в JSON. Например, самоссылочные объекты JS не могут быть строковыми.
Джон
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
Не могли бы вы объяснить, почему это так much better?
Дементик
3
Если метод предназначен для выполнения чего-либо, кроме log, вы должны проверить на null, null по-прежнему является объектом.
wi1
3
@ wi1 Согласен с тобой, могу проверить!!o[i] && typeof o[i] == 'object'
плов
32

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

https://npmjs.org/package/traverse

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

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

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

Бенджамин Аткин
источник
1
js-traverse также доступен через npm в node.js.
Ville
Да. Это просто называется траверс. И у них есть прекрасная веб-страница! Обновление моего ответа, чтобы включить его.
Бенджамин Аткин
15

Зависит от того, что вы хотите сделать. Вот пример обхода дерева объектов 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
Брайан Кэмпбелл
источник
9

Если вы просматриваете фактическую строку 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)
})
Дэвид Лейн
источник
8

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

Джон
источник
Отличный ответ! Можно ли возвращать пути, такие как abc, abcd и т. Д. Для каждого пройденного ключа?
supersan
1
@supersan вы можете просмотреть мои обновленные фрагменты кода. Я добавил переменную пути к каждому массиву строк. Строки в массиве представляют каждый ключ, к которому был получен доступ для получения итогового итерированного значения из исходного исходного объекта.
Джон
4

Я хотел использовать идеальное решение @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);
Raf
источник
2

Большинство движков 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

Мой сценарий:

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"]
Мохидин бин Мухаммед
источник
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>

Seung
источник
сделал это , чтобы представить форму ENCTYPE applicatioin / JSON
Seung
-1

Лучшее решение для меня было следующее:

просто и без использования каких-либо рамок

    var doSomethingForAll = function (arg) {
       if (arg != undefined && arg.length > 0) {
            arg.map(function (item) {
                  // do something for item
                  doSomethingForAll (item.subitem)
             });
        }
     }
Asqan
источник
-1

Вы можете получить все ключи / значения и сохранить иерархию с этим

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

Рики
источник
-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

Я создал библиотеку для обхода и редактирования глубоко вложенных объектов 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}в качестве третьего параметра.

И вы можете:

  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'}
dominik791
источник