Me gustaría atravesar un árbol de objetos JSON, pero no puedo encontrar ninguna biblioteca para eso. No parece difícil, pero se siente como reinventar la rueda.
En XML hay muchos tutoriales que muestran cómo atravesar un árbol XML con DOM :(
Me gustaría atravesar un árbol de objetos JSON, pero no puedo encontrar ninguna biblioteca para eso. No parece difícil, pero se siente como reinventar la rueda.
En XML hay muchos tutoriales que muestran cómo atravesar un árbol XML con DOM :(
Respuestas:
Si crees que jQuery es una exageración para una tarea tan primitiva, podrías hacer algo así:
//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 de la función de destino, mientras que o
debería ser el primer parámetro de la función. Sin embargo, establecerlo en this
(que sería la traverse
función) es un poco extraño, pero no es como process
usar la this
referencia de todos modos. Bien podría haber sido nulo.
/*jshint validthis: true */
arriba func.apply(this,[i,o[i]]);
para evitar el error W040: Possible strict violation.
causado por el uso dethis
traverse
función que rastrea la profundidad. Wenn llamando recursivamente agrega 1 al nivel actual.
Un objeto JSON es simplemente un objeto Javascript. En realidad, eso es lo que JSON significa: notación de objetos JavaScript. Por lo tanto, atravesaría un objeto JSON, sin embargo, elegiría "atravesar" un objeto Javascript en general.
En ES2017 harías:
Object.entries(jsonObj).forEach(([key, value]) => {
// do something with key and val
});
Siempre puedes escribir una función para descender recursivamente al 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 debería ser un buen punto de partida. Recomiendo encarecidamente utilizar métodos javascript modernos para tales cosas, ya que hacen que escribir dicho código sea mucho más fácil.
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'
Hay una nueva biblioteca para atravesar datos JSON con JavaScript que admite muchos casos de uso diferentes.
https://npmjs.org/package/traverse
https://github.com/substack/js-traverse
Funciona con todo tipo de objetos JavaScript. Incluso detecta ciclos.
También proporciona la ruta de cada nodo.
Depende de lo que quieras hacer. Aquí hay un ejemplo de atravesar un árbol de objetos JavaScript, imprimir claves y valores a medida que avanza:
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
Si está atravesando una cadena JSON real , puede usar una función de 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)
})
Al atravesar un 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)
})
EDITAR : Todos los ejemplos a continuación en esta respuesta se han editado para incluir una nueva variable de ruta producida por el iterador según la solicitud de @ supersan . La variable de ruta es una matriz de cadenas donde cada cadena en la matriz representa cada clave a la que se accedió para obtener el valor iterado resultante del objeto fuente original. La variable de ruta se puede alimentar a la función / método get de lodash . O podría escribir su propia versión de lodash get que maneja solo matrices de esta manera:
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"]));
EDITAR : Esta respuesta editada resuelve recorridos en bucle infinito.
Esta respuesta editada aún proporciona uno de los beneficios adicionales de mi respuesta original, que le permite usar la función de generador proporcionada para usar una interfaz iterativa más limpia y simple (piense en usar for of
bucles como en for(var a of b)
donde b
es iterable y a
es un elemento del iterable). ) Al usar la función de generador, además de ser una API más simple, también ayuda con la reutilización del código al hacer que no tenga que repetir la lógica de iteración en todas partes donde desee iterar profundamente en las propiedades de un objeto y también hace posiblebreak
salir de el bucle si desea detener la iteración antes.
Una cosa que noté que no se ha abordado y que no está en mi respuesta original es que debe tener cuidado al atravesar objetos arbitrarios (es decir, cualquier conjunto "aleatorio"), porque los objetos JavaScript pueden ser auto-referenciados. Esto crea la oportunidad de tener recorridos en bucle infinito. Sin embargo, los datos JSON no modificados no pueden ser auto-referenciados, por lo que si está utilizando este subconjunto particular de objetos JS, no tiene que preocuparse por los recorridos de bucle infinito y puede consultar mi respuesta original u otras respuestas. Aquí hay un ejemplo de un recorrido sin fin (tenga en cuenta que no es un código ejecutable, porque de lo contrario se bloquearía la pestaña del navegador).
También en el objeto generador en mi ejemplo editado, opté por usar en Object.keys
lugar de for in
que itera solo claves no prototipo en el objeto. Puede cambiar esto usted mismo si desea que se incluyan las claves prototipo. Vea mi sección de respuestas original a continuación para ambas implementaciones con Object.keys
y 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 salvarse de esto, puede agregar un conjunto dentro de un cierre, de modo que cuando se llama a la función por primera vez, comience a construir una memoria de los objetos que ha visto y no continúe la iteración una vez que se encuentre con un objeto ya visto. El siguiente fragmento de código hace eso y, por lo tanto, maneja casos de bucle 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 obtener una forma más nueva de hacerlo si no le importa descartar IE y admitir principalmente navegadores más actuales (consulte la tabla es6 de kangax para ver la compatibilidad). Puede usar generadores es2015 para esto. He actualizado la respuesta de @ TheHippo en consecuencia. Por supuesto, si realmente desea el soporte de IE, puede usar el transpilador JavaScript de 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);
}
Si solo desea propiedades propias enumerables (básicamente propiedades de cadena no prototipo), puede cambiarlo para iterar usando Object.keys
y un for...of
bucle en su lugar:
//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);
}
Quería usar la solución perfecta de @TheHippo en una función anónima, sin el uso de procesos y funciones de activación. Lo siguiente funcionó para mí, compartiendo para programadores novatos como yo.
(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);
La mayoría de los motores de Javascript no optimizan la recursividad de cola (esto podría no ser un problema si su JSON no está profundamente anidado), pero por lo general me equivoco con precaución y en su lugar hago iteraciones, por ejemplo
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)
Mi guión:
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;
};
Entrada 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": []
}
]
}
]
}
]
Llamada de función:
callback_func(inp_json);
Salida según mi necesidad:
["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>
Puede obtener todas las claves / valores y preservar la jerarquía con esto
// 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 es una modificación en ( https://stackoverflow.com/a/25063574/1484447 )
He creado una biblioteca para atravesar y editar objetos JS profundamente anidados. Echa un vistazo a la API aquí: https://github.com/dominik791
También puedes jugar con la biblioteca de manera interactiva usando la aplicación de demostración: https://dominik791.github.io/obj-traverse-demo/
Ejemplos de uso: siempre debe tener un objeto raíz, que es el primer parámetro de cada método:
var rootObj = {
name: 'rootObject',
children: [
{
'name': 'child1',
children: [ ... ]
},
{
'name': 'child2',
children: [ ... ]
}
]
};
El segundo parámetro es siempre el nombre de la propiedad que contiene los objetos anidados. En el caso anterior sería 'children'
.
El tercer parámetro es un objeto que utiliza para buscar objetos / objetos que desea encontrar / modificar / eliminar. Por ejemplo, si está buscando un objeto con una identificación igual a 1, pasará { id: 1}
como tercer parámetro.
Y tu puedes:
findFirst(rootObj, 'children', { id: 1 })
para encontrar el primer objeto con id === 1
findAll(rootObj, 'children', { id: 1 })
para encontrar todos los objetos con id === 1
findAndDeleteFirst(rootObj, 'children', { id: 1 })
para eliminar el primer objeto coincidentefindAndDeleteAll(rootObj, 'children', { id: 1 })
para eliminar todos los objetos coincidentesreplacementObj
se usa como el último parámetro en dos últimos métodos:
findAndModifyFirst(rootObj, 'children', { id: 1 }, { id: 2, name: 'newObj'})
para cambiar el primer objeto encontrado con id === 1
el{ id: 2, name: 'newObj'}
findAndModifyAll(rootObj, 'children', { id: 1 }, { id: 2, name: 'newObj'})
para cambiar todos los objetos con id === 1
el{ id: 2, name: 'newObj'}