Gostaria de percorrer uma árvore de objetos JSON, mas não consigo encontrar nenhuma biblioteca para isso. Não parece difícil, mas parece reinventar a roda.
No XML, existem muitos tutoriais mostrando como percorrer uma árvore XML com o DOM :(
Gostaria de percorrer uma árvore de objetos JSON, mas não consigo encontrar nenhuma biblioteca para isso. Não parece difícil, mas parece reinventar a roda.
No XML, existem muitos tutoriais mostrando como percorrer uma árvore XML com o DOM :(
Respostas:
Se você acha que o jQuery é um exagero para uma tarefa tão primitiva, você pode fazer algo assim:
//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);
this
valor na função de destino, enquanto que o
deve ser o primeiro parâmetro para a função. Configurá-lo para this
(qual seria a traverse
função) é um pouco estranho, mas não é como se process
usasse a this
referência de qualquer maneira. Poderia muito bem ter sido nulo.
/*jshint validthis: true */
acima func.apply(this,[i,o[i]]);
para evitar o erro W040: Possible strict violation.
causado pelo uso dethis
traverse
função que rastreia a profundidade. Quando chamarmos recursivamente, adicione 1 ao nível atual.
Um objeto JSON é simplesmente um objeto Javascript. Na verdade, é isso que JSON significa: JavaScript Object Notation. Portanto, você atravessaria um objeto JSON, no entanto, escolheria "atravessar" um objeto Javascript em geral.
No ES2017, você faria:
Object.entries(jsonObj).forEach(([key, value]) => {
// do something with key and val
});
Você sempre pode escrever uma função para descer recursivamente ao objeto:
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
}
}
Este deve ser um bom ponto de partida. Eu recomendo o uso de métodos javascript modernos para essas coisas, pois eles facilitam a escrita desse código.
function traverse(jsonObj) { if(jsonObj && typeof jsonObj == "object" ) { ...
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]);
}
}
}
much better
?
!!o[i] && typeof o[i] == 'object'
Há uma nova biblioteca para atravessar dados JSON com JavaScript que suporta muitos casos de uso diferentes.
https://npmjs.org/package/traverse
https://github.com/substack/js-traverse
Funciona com todos os tipos de objetos JavaScript. Ele até detecta ciclos.
Ele fornece o caminho de cada nó também.
Depende do que você quer fazer. Aqui está um exemplo de como percorrer uma árvore de objetos JavaScript, imprimir chaves e valores conforme segue:
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
Se você estiver atravessando uma string JSON real , poderá usar uma função 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)
})
Ao atravessar um objeto:
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)
})
EDIT : Todos os exemplos abaixo nesta resposta foram editados para incluir uma nova variável de caminho produzida pelo iterador conforme a solicitação do @ supersan . A variável path é uma matriz de cadeias em que cada cadeia na matriz representa cada chave acessada para obter o valor iterado resultante do objeto de origem original. A variável path pode ser inserida na função / método get do lodash . Ou você pode escrever sua própria versão do get do lodash, que lida apenas com matrizes da seguinte forma:
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 : Esta resposta editada resolve percursos de loop infinito.
Esta resposta editada ainda fornece um dos benefícios adicionais da minha resposta original, que permite que você use a função de gerador fornecida para usar uma interface iterável mais limpa e simples (pense em usar for of
loops como for(var a of b)
onde b
é um iterável e a
é um elemento do iterável ) Usando a função de gerador além de ser uma API simples que também ajuda com a reutilização de código, tornando-a assim que você não tem que repetir a lógica iteração em todos os lugares que você quer iterate profundamente sobre as propriedades do objeto e também torna possível break
fora do o loop se você gostaria de interromper a iteração anteriormente.
Uma coisa que noto que não foi abordada e que não está na minha resposta original é que você deve ter cuidado ao atravessar objetos arbitrários (ou seja, qualquer conjunto "aleatório" de)), porque os objetos JavaScript podem ser auto-referenciados. Isso cria a oportunidade de ter percursos de loop infinitos. Dados JSON não modificados, no entanto, não podem ser auto-referenciados; portanto, se você estiver usando esse subconjunto específico de objetos JS, não precisará se preocupar com percursos de loop infinito e poderá consultar minha resposta original ou outras respostas. Aqui está um exemplo de uma travessia sem fim (observe que não é um trecho de código executável, porque, caso contrário, poderia travar a guia do navegador).
Também no objeto gerador no meu exemplo editado, optei por usar, em Object.keys
vez de for in
iterar apenas chaves não protótipo no objeto. Você pode trocar isso sozinho se quiser incluir as chaves do protótipo. Veja minha seção de resposta original abaixo para ambas as implementações com Object.keys
e 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);
}
Para se salvar disso, você pode adicionar um conjunto em um fechamento, para que, quando a função for chamada pela primeira vez, comece a criar uma memória dos objetos que viu e não continue a iteração depois de encontrar um objeto já visto. O trecho de código abaixo faz isso e, portanto, lida com casos de loop infinito.
//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);
}
Para uma maneira mais nova de fazer isso, se você não se importa em deixar o IE e principalmente oferecer suporte a navegadores mais atuais (verifique a tabela es6 da kangax para compatibilidade). Você pode usar os geradores es2015 para isso. Atualizei a resposta do @ TheHippo de acordo. Obviamente, se você realmente deseja suporte ao IE, pode usar o transpiler 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);
}
Se você deseja apenas propriedades enumeráveis (basicamente propriedades de cadeia que não são protótipos), é possível alterá-lo para iterar usando Object.keys
um for...of
loop:
//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);
}
Eu queria usar a solução perfeita do @TheHippo em uma função anônima, sem o uso de funções de processo e gatilho. O seguinte funcionou para mim, compartilhando para programadores iniciantes como eu.
(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);
A maioria dos mecanismos Javascript não otimiza a recursão da cauda (isso pode não ser um problema se o seu JSON não estiver profundamente aninhado), mas eu geralmente erro por precaução e faço a iteração, por exemplo
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)
Meu script:
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 de entrada:
[
{
"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": []
}
]
}
]
}
]
Chamada de função:
callback_func(inp_json);
Saída conforme minha necessidade:
["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>
Você pode obter todas as chaves / valores e preservar a hierarquia com este
// 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')
Esta é uma modificação em ( https://stackoverflow.com/a/25063574/1484447 )
Eu criei uma biblioteca para percorrer e editar objetos JS aninhados profundos. Confira a API aqui: https://github.com/dominik791
Você também pode brincar com a biblioteca interativamente usando o aplicativo demo: https://dominik791.github.io/obj-traverse-demo/
Exemplos de uso: você sempre deve ter um objeto raiz, que é o primeiro parâmetro de cada método:
var rootObj = {
name: 'rootObject',
children: [
{
'name': 'child1',
children: [ ... ]
},
{
'name': 'child2',
children: [ ... ]
}
]
};
O segundo parâmetro é sempre o nome da propriedade que contém objetos aninhados. No caso acima, seria'children'
.
O terceiro parâmetro é um objeto que você usa para encontrar objetos que deseja localizar / modificar / excluir. Por exemplo, se você estiver procurando por objetos com um ID igual a 1, passará{ id: 1}
como o terceiro parâmetro.
E você pode:
findFirst(rootObj, 'children', { id: 1 })
para encontrar o primeiro objeto com id === 1
findAll(rootObj, 'children', { id: 1 })
para encontrar todos os objetos com id === 1
findAndDeleteFirst(rootObj, 'children', { id: 1 })
excluir o primeiro objeto correspondentefindAndDeleteAll(rootObj, 'children', { id: 1 })
excluir todos os objetos correspondentesreplacementObj
é usado como o último parâmetro em dois últimos métodos:
findAndModifyFirst(rootObj, 'children', { id: 1 }, { id: 2, name: 'newObj'})
para alterar o primeiro objeto encontrado com id === 1
para o{ id: 2, name: 'newObj'}
findAndModifyAll(rootObj, 'children', { id: 1 }, { id: 2, name: 'newObj'})
para alterar todos os objetos com id === 1
para o{ id: 2, name: 'newObj'}