JavaScript를 사용하여 JSON 객체 트리의 모든 노드를 순회


148

JSON 객체 트리를 통과하고 싶지만 해당 라이브러리를 찾을 수 없습니다. 어렵지는 않지만 휠을 재발 명하는 것처럼 느껴집니다.

XML에는 DOM을 사용하여 XML 트리를 순회하는 방법을 보여주는 많은 자습서가 있습니다.


1
iterator IIFE를 github.com/eltomjan/ETEhomeTools/blob/master/HTM_HTA/로 만들었 습니다. 다음으로 사전 정의 된 (기본) DepthFirst & BreadthFirst와 재귀없이 JSON 구조 내로 이동할 수있는 기능이 있습니다.
Tom

답변:


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 (this, ...)가 왜 필요한가요? func.apply (o, ...)이어야합니까?
Craig Celeste

4
@ParchedSquid No. apply ()에 대한 API 문서를 보면 첫 번째 매개 변수는 this대상 함수 의 값이지만 함수 o의 첫 번째 매개 변수 여야합니다. this( traverse기능 이 될 것)으로 설정하는 것은 약간 이상하지만 어쨌든 참조를 process사용하는 것과 this다릅니다. 또한 null 일 수도 있습니다.
Thor84no

1
엄격 모드에서 jshint 위해 당신은 추가해야 할 수도 있지만 /*jshint validthis: true */위의 func.apply(this,[i,o[i]]);오류 방지하기 위해 W040: Possible strict violation.의 사용으로 인한this
Jasdeep Khalsa

4
@jasdeepkhalsa : 맞습니다. 그러나 답을 쓰는 시점에서 jshint는 1 년 반 동안 프로젝트로 시작되지 않았습니다.
TheHippo

1
@Vishal 당신은 traverse깊이를 추적하는 기능에 3 매개 변수를 추가 할 수 있습니다 . Wenn 호출은 재귀 적으로 1을 현재 레벨에 추가합니다.
TheHippo

75

JSON 객체는 단순히 자바 스크립트 객체입니다. 이것이 실제로 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
    }
}

좋은 출발점이되어야합니다. 그런 코드를 작성하는 것이 훨씬 쉬워지기 때문에 현대적인 자바 스크립트 메소드를 사용하는 것이 좋습니다.


9
(typeof null == "object") === true이므로 v == null 인 traverse (v)를 피하십시오. function traverse(jsonObj) { if(jsonObj && typeof jsonObj == "object" ) { ...
Marcelo Amorim

4
나는 pedantic하는 것을 싫어하지만, 나는 이것에 대해 이미 많은 혼란이 있다고 생각하므로 명확성을 위해 다음과 같이 말합니다. JSON과 JavaScript 객체는 동일하지 않습니다. JSON은 JavaScript 객체의 형식을 기반으로하지만 JSON은 단지 표기법입니다 . 객체를 나타내는 문자열입니다. 모든 JSON을 JS 객체로 "구문 분석"할 수 있지만 모든 JS 객체를 JSON으로 "문자열"할 수는 없습니다. 예를 들어 자체 참조 JS 객체는 문자열화할 수 없습니다.
John

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
메소드가 로그 이외의 다른 작업을 수행하려는 경우 널을 점검해야하며, 널은 여전히 ​​오브젝트입니다.
wi1

3
@ wi1 당신과 함께, 확인할 수 있습니다!!o[i] && typeof o[i] == 'object'
pilau

32

여러 가지 사용 사례를 지원하는 JavaScript로 JSON 데이터를 순회하기위한 새로운 라이브러리가 있습니다.

https://npmjs.org/package/traverse

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

모든 종류의 JavaScript 객체와 함께 작동합니다. 심지어주기를 감지합니다.

각 노드의 경로도 제공합니다.


1
js-traverse는 node.js의 npm을 통해 사용할 수있는 것 같습니다.

예. 그냥 거기를 트래버스라고 부릅니다. 그리고 그들은 멋진 웹 페이지를 가지고 있습니다! 포함하도록 내 답변을 업데이트합니다.
Benjamin Atkin

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

편집 :이 답변의 아래 모든 예제는 @supersan의 요청에 따라 반복기에서 생성 된 새로운 경로 변수를 포함하도록 편집되었습니다 . 경로 변수는 배열의 각 문자열이 원래 소스 오브젝트에서 결과 반복 값에 도달하기 위해 액세스 된 각 키를 나타내는 문자열 배열입니다. 경로 변수는 lodash의 get function / method에 제공 될 수 있습니다 . 또는 배열 만 처리하는 lodash의 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"]));

편집 :이 편집 된 답변은 무한 반복 횡단을 해결합니다.

성가신 무한 객체 탐색 중지

이 편집 대답은 여전히 제공된 사용할 수 있습니다 내 원래의 대답의 추가 혜택 중 하나를 제공 발전기 기능을 깨끗하고 간단하게 사용하기 위해서는 반복 가능한 인터페이스를 (사용하여 생각 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를 삭제하지 않고 주로 최신 브라우저를 지원하는 것이 마음에 들지 않으면 새로운 방법을 사용할 수 있습니다 ( 호환성에 대해서는 kangax의 es6 테이블 을 확인하십시오 ). 이를 위해 es2015 생성기 를 사용할 수 있습니다 . @TheHippo의 답변을 적절하게 업데이트했습니다. 물론 IE 지원을 정말로 원한다면 babel JavaScript transpiler를 사용할 수 있습니다 .

//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 당신은 내 업데이트 된 코드 스 니펫을 볼 수 있습니다. 문자열 배열 인 각각에 경로 변수를 추가했습니다. 배열의 문자열은 원래 소스 오브젝트에서 결과 반복 값에 도달하기 위해 액세스 된 각 키를 나타냅니다.
John

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

대부분의 자바 스크립트 엔진은 꼬리 재귀를 최적화하지 않습니다 (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
seung

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

사용법 예 : 항상 각 메소드의 첫 번째 매개 변수 인 루트 오브젝트가 있어야합니다.

var rootObj = {
  name: 'rootObject',
  children: [
    {
      'name': 'child1',
       children: [ ... ]
    },
    {
       'name': 'child2',
       children: [ ... ]
    }
  ]
};

두 번째 매개 변수는 항상 중첩 개체를 보유하는 속성의 이름입니다. 위의 경우입니다 'children'.

세 번째 매개 변수는 찾고 / 수정 / 삭제하려는 개체 / 개체를 찾는 데 사용하는 개체입니다. 예를 들어 id가 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'}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.