IT story

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

hot-time 2020. 6. 23. 07:24
반응형

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


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

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


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

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

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


function traverse(o ) {
    for (i in o) {
        if (!!o[i] && typeof(o[i])=="object") {
            console.log(i, o[i])
            traverse(o[i] );
        }
    }
}

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

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

편집 : 모든 답변은 @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);
}


프로세스와 트리거 함수를 사용하지 않고 @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);

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


나에게 가장 좋은 해결책은 다음과 같습니다.

간단하고 프레임 워크를 사용하지 않고

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

이것으로 모든 키 / 값을 얻고 계층을 유지할 수 있습니다

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

세 번째 매개 변수는 찾고 / 수정 / 삭제하려는 개체 / 개체를 찾는 데 사용하는 개체입니다. 예를 들어 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'}

참고 URL : https://stackoverflow.com/questions/722668/traverse-all-the-nodes-of-a-json-object-tree-with-javascript

반응형