Обведіть усі вузли дерева об’єктів 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);

2
Чому fund.apply (це, ...)? Чи не повинно бути забавно. Застосовуйте (о, ...)?
Крейг Селесте

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. Таким чином, ви обходите об'єкт 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
Уникайте траверсу (v), де v == null, тому що (typeof null == "об'єкт") === 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
Якщо метод призначений зробити що-небудь, крім журналу, ви повинні перевірити наявність null, null все ще є об'єктом.
wi1

3
@ wi1 Згоден з вами, міг би перевірити!!o[i] && typeof o[i] == 'object'
pilau

32

Існує нова бібліотека для переміщення даних JSON з JavaScript, яка підтримує безліч різних випадків використання.

https://npmjs.org/package/traverse

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

Він працює з усіма видами об’єктів JavaScript. Він навіть виявляє цикли.

Він також забезпечує шлях кожного вузла.


1
Здається, js-traverse також доступний через npm у node.js.
Віль

Так. Це просто називається траверсом туди. І у них прекрасна веб-сторінка! Оновлення моєї відповіді, щоб включити її.
Бенджамін Аткін

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, тоді ви можете скористатися функцією оживлення.

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 . Змінна шлях - це масив рядків, де кожна рядок у масиві являє собою кожний ключ, до якого можна було отримати доступ до отриманого ітераційного значення з вихідного об'єкта-джерела. Змінна контура може бути подана у функцію / метод getash getash . Або ви можете написати свою версію getash's get, яка обробляє лише масиви на зразок:

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 : Ця відредагована відповідь вирішує нескінченне циклічне пересування.

Зупинка Пескіських нескінченних об'їзних обходів

Цей відредагований відповідь все ще надає одну з додаткових переваг моєї оригінальної відповіді, яка дозволяє використовувати надану функцію генератора , щоб використовувати більш чистий та простий ітерабельний інтерфейс (подумайте, використовуючи 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 і підтримувати в основному більш сучасні браузери (перевірте сумісність таблиці kengax в сумі сумісність). Для цього можна використовувати генератори es2015 . Я відповідно оновив відповідь @ TheHippo. Звичайно, якщо вам дуже потрібна підтримка IE, ви можете скористатись транслятором 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);
}

Якщо ви хочете тільки власні перелічуваних властивості ( в основному властивості без ланцюга прототипів) , ви можете змінити його ітерація , використовуючи 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);

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>


змусив її надіслати форму enctype applicatioin / json
вересня 1717

-1

Найкращим рішенням для мене було таке:

простий і без використання будь-яких рамок

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

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

Приклади використання: Ви завжди повинні мати root об'єкт, який є першим параметром кожного методу:

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'}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.