Pregunta Objetos de acceso / proceso (anidados), matrices o JSON


Tengo una estructura de datos anidados que contiene objetos y matrices. ¿Cómo puedo extraer la información, es decir, acceder a valores (o claves) específicos o múltiples?

Por ejemplo:

var data = {
    code: 42,
    items: [{
        id: 1,
        name: 'foo'
    }, {
        id: 2,
        name: 'bar'
    }]
};

¿Cómo podría acceder al name del segundo artículo en items?


636
2017-08-12 13:02


origen


Respuestas:


Preliminares

JavaScript tiene solo un tipo de datos que puede contener múltiples valores: Objeto. Un Formación es una forma especial de objeto.

(Normal) Los objetos tienen la forma

{key: value, key: value, ...}

Las matrices tienen la forma

[value, value, ...]

Ambas matrices y objetos exponen una key -> value estructura. Las claves en una matriz deben ser numéricas, mientras que cualquier cadena se puede usar como clave en los objetos. Los pares clave-valor también se llaman "propiedades".

Se puede acceder a las propiedades ya sea usando notación de puntos

const value = obj.someProperty;

o notación de paréntesis, si el nombre de la propiedad no sería un JavaScript válido nombre del identificador [especulación], o el nombre es el valor de una variable:

// the space is not a valid character in identifier names
const value = obj["some Property"];

// property name as variable
const name = "some Property";
const value = obj[name];

Por esa razón, solo se puede acceder a los elementos de la matriz mediante la notación de corchetes:

const value = arr[5]; // arr.5 would be a syntax error

// property name / index as variable
const x = 5;
const value = arr[x];

Espera ... ¿qué pasa con JSON?

JSON es una representación textual de datos, al igual que XML, YAML, CSV y otros. Para trabajar con esos datos, primero tiene que convertirse a tipos de datos de JavaScript, es decir, matrices y objetos (y cómo se acaba de explicar cómo trabajar con ellos). Cómo analizar el JSON se explica en la pregunta Parse JSON en JavaScript? .

Leer más material

Cómo acceder a matrices y objetos es conocimiento fundamental de JavaScript y, por lo tanto, es recomendable leer el Guía de JavaScript de MDN, especialmente las secciones



Acceso a estructuras de datos anidados

Una estructura de datos anidados es una matriz u objeto que se refiere a otras matrices u objetos, es decir, sus valores son matrices u objetos. Se puede acceder a dichas estructuras aplicando consecutivamente la notación de puntos o corchetes.

Aquí hay un ejemplo:

const data = {
    code: 42,
    items: [{
        id: 1,
        name: 'foo'
    }, {
        id: 2,
        name: 'bar'
    }]
};

Supongamos que queremos acceder al name del segundo artículo.

Aquí es cómo podemos hacerlo paso a paso:

Como podemos ver data es un objeto, por lo tanto, podemos acceder a sus propiedades usando la notación de puntos. los items la propiedad se accede de la siguiente manera:

data.items

El valor es una matriz, para acceder a su segundo elemento, debemos usar la notación de corchetes:

data.items[1]

Este valor es un objeto y utilizamos la notación de puntos nuevamente para acceder al name propiedad. Así que eventualmente obtenemos:

const item_name = data.items[1].name;

Alternativamente, podríamos haber usado la notación de corchetes para cualquiera de las propiedades, especialmente si el nombre contenía caracteres que lo hubieran invalidado para el uso de la notación de puntos:

const item_name = data['items'][1]['name'];

Intento acceder a una propiedad pero solo obtengo undefined ¿espalda?

La mayoría de las veces cuando recibes undefined, el objeto / matriz simplemente no tiene una propiedad con ese nombre.

const foo = {bar: {baz: 42}};
console.log(foo.baz); // undefined

Utilizar console.log o console.dir e inspeccionar la estructura del objeto / matriz. La propiedad a la que intenta acceder podría estar realmente definida en un objeto / matriz anidada.

console.log(foo.bar.baz); // 42

¿Qué pasa si los nombres de las propiedades son dinámicos y no los conozco de antemano?

Si los nombres de las propiedades son desconocidos o queremos acceder a todas las propiedades de un objeto / elementos de una matriz, podemos utilizar el for...in  [MDN] bucle para objetos y el for  [MDN] bucle para matrices para iterar sobre todas las propiedades / elementos.

Objetos

Para iterar sobre todas las propiedades de data, podemos iterar sobre el objeto al igual que:

for (const prop in data) {
    // `prop` contains the name of each property, i.e. `'code'` or `'items'`
    // consequently, `data[prop]` refers to the value of each property, i.e.
    // either `42` or the array
}

Según el origen del objeto (y lo que desea hacer), es posible que deba probar en cada iteración si la propiedad es realmente una propiedad del objeto o si es una propiedad heredada. Puedes hacer esto con Object#hasOwnProperty  [MDN].

Como alternativa a for...in con hasOwnProperty, puedes usar Object.keys  [MDN] para obtener un matriz de nombres de propiedad:

Object.keys(data).forEach(function(prop) {
  // `prop` is the property name
  // `data[prop]` is the property value
});

Arrays

Para iterar sobre todos los elementos del data.items  formación, usamos un for lazo:

for(let i = 0, l = data.items.length; i < l; i++) {
    // `i` will take on the values `0`, `1`, `2`,..., i.e. in each iteration
    // we can access the next element in the array with `data.items[i]`, example:
    // 
    // var obj = data.items[i];
    // 
    // Since each element is an object (in our example),
    // we can now access the objects properties with `obj.id` and `obj.name`. 
    // We could also use `data.items[i].id`.
}

Uno también podría usar for...in para iterar sobre las matrices, pero hay razones por las que esto debe evitarse: ¿Por qué 'para (var item in list)' con arrays se considera una mala práctica en JavaScript?.

Con el creciente soporte de navegador de ECMAScript 5, el método de matriz forEach  [MDN] se convierte en una alternativa interesante también:

data.items.forEach(function(value, index, array) {
    // The callback is executed for each element in the array.
    // `value` is the element itself (equivalent to `array[index]`)
    // `index` will be the index of the element in the array
    // `array` is a reference to the array itself (i.e. `data.items` in this case)
}); 

En entornos compatibles con ES2015 (ES6), también puede usar for...of  [MDN] loop, que no solo funciona para arrays, sino para cualquier iterable:

for (const item of data.items) {
   // `item` is the array element, **not** the index
}

En cada iteración, for...of directamente nos da el siguiente elemento de iterable, no hay un "índice" para acceder o usar.


¿Qué pasa si la "profundidad" de la estructura de datos es desconocida para mí?

Además de las claves desconocidas, la "profundidad" de la estructura de datos (es decir, cuántos objetos anidados) tiene, podría ser desconocida también. Cómo acceder a propiedades profundamente anidadas generalmente depende de la estructura de datos exacta.

Pero si la estructura de datos contiene patrones repetitivos, p. la representación de un árbol binario, la solución generalmente incluye recursivamente  [Wikipedia] acceder a cada nivel de la estructura de datos.

Aquí hay un ejemplo para obtener el primer nodo de hoja de un árbol binario:

function getLeaf(node) {
    if (node.leftChild) {
        return getLeaf(node.leftChild); // <- recursive call
    }
    else if (node.rightChild) {
        return getLeaf(node.rightChild); // <- recursive call
    }
    else { // node must be a leaf node
        return node;
    }
}

const first_leaf = getLeaf(root);

const root = {
    leftChild: {
        leftChild: {
            leftChild: null,
            rightChild: null,
            data: 42
        },
        rightChild: {
            leftChild: null,
            rightChild: null,
            data: 5
        }
    },
    rightChild: {
        leftChild: {
            leftChild: null,
            rightChild: null,
            data: 6
        },
        rightChild: {
            leftChild: null,
            rightChild: null,
            data: 7
        }
    }
};
function getLeaf(node) {
    if (node.leftChild) {
        return getLeaf(node.leftChild);
    } else if (node.rightChild) {
        return getLeaf(node.rightChild);
    } else { // node must be a leaf node
        return node;
    }
}

console.log(getLeaf(root).data);

Una forma más genérica de acceder a una estructura de datos anidados con claves y profundidad desconocidas es probar el tipo del valor y actuar en consecuencia.

Aquí hay un ejemplo que agrega todos los valores primitivos dentro de una estructura de datos anidados a una matriz (suponiendo que no contenga ninguna función). Si encontramos un objeto (o matriz) simplemente llamamos toArray de nuevo en ese valor (llamada recursiva).

function toArray(obj) {
    const result = [];
    for (const prop in obj) {
        const value = obj[prop];
        if (typeof value === 'object') {
            result.push(toArray(value)); // <- recursive call
        }
        else {
            result.push(value);
        }
    }
    return result;
}

const data = {
  code: 42,
  items: [{
    id: 1,
    name: 'foo'
  }, {
    id: 2,
    name: 'bar'
  }]
};


function toArray(obj) {
  const result = [];
  for (const prop in obj) {
    const value = obj[prop];
    if (typeof value === 'object') {
      result.push(toArray(value));
    } else {
      result.push(value);
    }
  }
  return result;
}

console.log(toArray(data));



Ayudantes

Dado que la estructura de un objeto o matriz compleja no es necesariamente obvia, podemos inspeccionar el valor en cada paso para decidir cómo avanzar más. console.log  [MDN] y console.dir  [MDN] ayúdanos a hacer esto Por ejemplo (salida de la consola de Chrome):

> console.log(data.items)
 [ Object, Object ]

Aquí vemos que eso data.items es una matriz con dos elementos que son ambos objetos. En la consola de Chrome, los objetos pueden incluso expandirse e inspeccionarse de inmediato.

> console.log(data.items[1])
  Object
     id: 2
     name: "bar"
     __proto__: Object

Esto nos dice que data.items[1] es un objeto, y después de expandirlo vemos que tiene tres propiedades, id, name y __proto__. Esta última es una propiedad interna utilizada para la cadena prototipo del objeto. Sin embargo, el prototipo de cadena y herencia está fuera del alcance de esta respuesta.


878
2018-05-14 03:29



Puedes acceder de esta manera

data.items[1].name

o

data["items"][1]["name"]

Ambas formas son iguales.


48
2018-06-29 09:15



En caso de que intentes acceder a un item de la estructura de ejemplo por id o name, sin saber su posición en la matriz, la forma más fácil de hacerlo sería usar underscore.js biblioteca:

var data = {
    code: 42,
    items: [{
        id: 1,
        name: 'foo'
    }, {
        id: 2,
        name: 'bar'
    }]
};

_.find(data.items, function(item) {
  return item.id === 2;
});
// Object {id: 2, name: "bar"}

Desde mi experiencia, el uso de funciones de orden superior en lugar de for o for..in los bucles dan como resultado un código más fácil de razonar y, por lo tanto, más fácil de mantener.

Solo mis 2 centavos.


22
2017-08-18 19:20



A veces, acceder a un objeto anidado utilizando una cadena puede ser deseable. El enfoque simple es el primer nivel, por ejemplo

var obj = { hello: "world" };
var key = "hello";
alert(obj[key]);//world

Pero este no suele ser el caso con JSON complejo. A medida que Json se vuelve más complejo, los enfoques para encontrar valores dentro del json también se vuelven complejos. Un enfoque recursivo para navegar por el JSON es lo mejor, y la forma en que se aprovecha esa recursión dependerá del tipo de datos que se buscan. Si hay declaraciones condicionales involucradas, una json búsqueda puede ser una buena herramienta para usar.

Si la propiedad a la que se accede ya es conocida, pero la ruta es compleja, por ejemplo, en este objeto

var obj = {
 arr: [
    { id: 1, name: "larry" },    
    { id: 2, name: "curly" },
    { id: 3, name: "moe" }
 ]
};

Y sabes que quieres obtener el primer resultado de la matriz en el objeto, tal vez te gustaría usar

var moe = obj["arr[0].name"];

Sin embargo, eso causará una excepción ya que no hay propiedad del objeto con ese nombre. La solución para poder usar esto sería aplanar el aspecto del árbol del objeto. Esto puede hacerse recursivamente.

function flatten(obj){
 var root = {};
 (function tree(obj, index){
   var suffix = toString.call(obj) == "[object Array]" ? "]" : "";
   for(var key in obj){
    if(!obj.hasOwnProperty(key))continue;
    root[index+key+suffix] = obj[key];
    if( toString.call(obj[key]) == "[object Array]" )tree(obj[key],index+key+suffix+"[");
    if( toString.call(obj[key]) == "[object Object]" )tree(obj[key],index+key+suffix+".");   
   }
 })(obj,"");
 return root;
}

Ahora, el objeto complejo puede ser aplanado

var obj = previous definition;
var flat = flatten(obj);
var moe = flat["arr[0].name"];//moe

Aquí hay un jsFiddle Demo de este enfoque en uso.


15
2017-10-22 18:38



Los objetos y matrices tienen una gran cantidad de métodos integrados que pueden ayudarlo con el procesamiento de datos.

Nota: en muchos de los ejemplos que estoy usando funciones de flecha. Son similares a expresiones de función, pero atan el this valor léxico

Object.keys(), Object.values() (ES 2017) y Object.entries() (ES 2017)

Object.keys() devuelve una matriz de claves del objeto, Object.values() devuelve una matriz de valores de objeto, y Object.entries() devuelve una matriz de claves del objeto y los valores correspondientes en un formato [key, value].

const obj = {
  a: 1
 ,b: 2
 ,c: 3
}

console.log(Object.keys(obj)) // ['a', 'b', 'c']
console.log(Object.values(obj)) // [1, 2, 3]
console.log(Object.entries(obj)) // [['a', 1], ['b', 2], ['c', 3]]

Object.entries() con un bucle for-of y asignación de desestructuración

const obj = {
  a: 1
 ,b: 2
 ,c: 3
}

for (const [key, value] of Object.entries(obj)) {
  console.log(`key: ${key}, value: ${value}`)
}

Es muy conveniente repetir el resultado de Object.entries() con un ciclo for-of y asignación de desestructuración.

For-of loop le permite iterar los elementos de la matriz. La sintaxis es for (const element of array) (podemos reemplazar const con var o let, pero es mejor usar const si no tenemos la intención de modificar element)

La asignación de desestructuración le permite extraer valores de una matriz o un objeto y asignarlos a variables. En este caso const [key, value] significa que en lugar de asignar el [key, value] matriz para element, asignamos el primer elemento de esa matriz a key y el segundo elemento para value. Es equivalente a esto:

for (const element of Object.entries(obj)) {
  const key = element[0]
       ,value = element[1]
}

Como puede ver, la desestructuración hace que esto sea mucho más simple.

Array.prototype.every() y Array.prototype.some()

los every() método devuelve truesi la función de devolución de llamada especificada regresa true para cada elemento de la matriz. los some() método devuelve truesi la función de devolución de llamada especificada regresa true para algunos (al menos un) elemento.

const arr = [1, 2, 3]

// true, because every element is greater than 0
console.log(arr.every(x => x > 0))
// false, because 3^2 is greater than 5
console.log(arr.every(x => Math.pow(x, 2) < 5))
// true, because 2 is even (the remainder from dividing by 2 is 0)
console.log(arr.some(x => x % 2 === 0))
// false, because none of the elements is equal to 5
console.log(arr.some(x => x === 5))

Array.prototype.find() y Array.prototype.filter()

los find() métodos devuelve el primero elemento que satisface la función de devolución de llamada proporcionada. los filter() método devuelve una matriz de todas elementos que satisfacen la función de devolución de llamada proporcionada.

const arr = [1, 2, 3]

// 2, because 2^2 !== 2
console.log(arr.find(x => x !== Math.pow(x, 2)))
// 1, because it's the first element
console.log(arr.find(x => true))
// undefined, because none of the elements equals 7
console.log(arr.find(x => x === 7))

// [2, 3], because these elements are greater than 1
console.log(arr.filter(x => x > 1))
// [1, 2, 3], because the function returns true for all elements
console.log(arr.filter(x => true))
// [], because none of the elements equals neither 6 nor 7
console.log(arr.filter(x => x === 6 || x === 7))

Array.prototype.map()

los map() método devuelve una matriz con los resultados de llamar a una función de devolución de llamada proporcionada en los elementos de la matriz.

const arr = [1, 2, 3]

console.log(arr.map(x => x + 1)) // [2, 3, 4]
console.log(arr.map(x => String.fromCharCode(96 + x))) // ['a', 'b', 'c']
console.log(arr.map(x => x)) // [1, 2, 3] (no-op)
console.log(arr.map(x => Math.pow(x, 2))) // [1, 4, 9]
console.log(arr.map(String)) // ['1', '2', '3']

Array.prototype.reduce()

los reduce() método reduce una matriz a un único valor llamando a la función de devolución de llamada proporcionada con dos elementos.

const arr = [1, 2, 3]

// Sum of array elements.
console.log(arr.reduce((a, b) => a + b)) // 6
// The largest number in the array.
console.log(arr.reduce((a, b) => a > b ? a : b)) // 3

los reduce() método toma un segundo parámetro opcional, que es el valor inicial. Esto es útil cuando la matriz a la que llama reduce() puede tener cero o un elemento. Por ejemplo, si quisiéramos crear una función sum() que toma una matriz como argumento y devuelve la suma de todos los elementos, podríamos escribirla así:

const sum = arr => arr.reduce((a, b) => a + b, 0)

console.log(sum([]))     // 0
console.log(sum([4]))    // 4
console.log(sum([2, 5])) // 7


11
2018-05-06 10:45



Esta pregunta es bastante antigua, por lo que es una actualización contemporánea. Con el inicio de ES2015, existen alternativas para obtener los datos que necesita. Ahora hay una función llamada desestructuración de objetos para acceder a objetos anidados

const data = {
  code: 42,
  items: [{
    id: 1,
    name: 'foo'
  }, {
    id: 2,
    name: 'bar'
  }]
};

const {
  items: [, {
    name: secondName
  }]
} = data;

console.log(secondName);

El ejemplo anterior crea una variable llamada secondName desde el name clave de una matriz llamada items, el solitario , dice omita el primer objeto en la matriz.

En particular, es probable que sea excesivo para este ejemplo, ya que el acceso simple a la matriz es más fácil de leer, pero resulta útil cuando se separan objetos en general.

Esta es una introducción muy breve a su caso de uso específico, la desestructuración puede ser una sintaxis inusual para acostumbrarse al principio. Yo recomendaría leer Documentación de asignación de desestructuración de Mozilla aprender más.


8
2018-06-26 12:43



Utilizando JSONPath sería una de las soluciones más flexibles si está dispuesto a incluir una biblioteca: https://github.com/s3u/JSONPath (nodo y navegador)

Para su caso de uso, el camino json sería:

$..items[1].name

asi que:

var secondName = jsonPath.eval(data, "$..items[1].name");

6
2018-02-24 15:55



Yo prefiero JQuery. Es más limpio y fácil de leer.

 $.each($.parseJSON(data), function (key, value) {
    alert(value.<propertyname>);
});


4
2018-06-17 12:16