Objetos de access / processo (nesteds), matrizes ou JSON

Eu tenho uma estrutura de dados aninhada contendo objects e matrizes. Como posso extrair as informações, ou seja, acessar um valor específico ou vários valores (ou chaves)?

Por exemplo:

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

Como eu poderia acessar o name do segundo item em items ?

   

Preliminares

JavaScript tem apenas um tipo de dados que pode conter vários valores: Objeto . Um Array é uma forma especial de object.

(Simples) Objetos têm a forma

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

Arrays têm a forma

 [value, value, ...] 

Ambas as matrizes e objects expõem uma key -> value estrutura de key -> value . As chaves em uma matriz devem ser numéricas, enquanto qualquer sequência pode ser usada como chave em objects. Os pares de valores-chave também são chamados de “propriedades” .

As propriedades podem ser acessadas usando notação de ponto

 const value = obj.someProperty; 

ou notação de colchetes , se o nome da propriedade não for um nome de identificador JavaScript válido [spec] , ou o nome for o valor de uma variável:

 // 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 esse motivo, os elementos da matriz só podem ser acessados ​​usando a notação de colchetes:

 const value = arr[5]; // arr.5 would be a syntax error // property name / index as variable const x = 5; const value = arr[x]; 

Espere … e o JSON?

JSON é uma representação textual de dados, assim como XML, YAML, CSV e outros. Para trabalhar com esses dados, primeiro eles precisam ser convertidos em tipos de dados JavaScript, ou seja, matrizes e objects (e como trabalhar com eles foi explicado). Como analisar JSON é explicado na pergunta Parse JSON in JavaScript? .

Mais material de leitura

Como acessar arrays e objects é um conhecimento fundamental do JavaScript e, portanto, é aconselhável ler o Guia JavaScript MDN , especialmente as seções

  • Trabalhando com Objetos
  • Matrizes
  • Eloquent JavaScript – Estruturas de Dados


Acessando Estruturas de Dados Aninhadas

Uma estrutura de dados aninhada é uma matriz ou object que se refere a outras matrizes ou objects, ou seja, seus valores são matrizes ou objects. Tais estruturas podem ser acessadas pela aplicação consecutiva de notação de pontos ou colchetes.

Aqui está um exemplo:

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

Vamos supor que queremos acessar o name do segundo item.

Aqui está como podemos fazer isso passo a passo:

Como podemos ver os data é um object, portanto, podemos acessar suas propriedades usando a notação de ponto. A propriedade items é acessada da seguinte forma:

 data.items 

O valor é uma matriz, para acessar seu segundo elemento, temos que usar a notação de colchetes:

 data.items[1] 

Esse valor é um object e usamos a notação de ponto novamente para acessar a propriedade name . Então, nós finalmente conseguimos:

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

Alternativamente, poderíamos ter usado a notação de colchetes para qualquer uma das propriedades, especialmente se o nome continha caracteres que invalidariam o uso da notação de ponto:

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

Estou tentando acessar uma propriedade, mas só fico undefined volta?

Na maior parte do tempo, quando você está ficando undefined , o object / array simplesmente não possui uma propriedade com esse nome.

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

Use console.log ou console.dir e inspecione a estrutura do object / matriz. A propriedade que você está tentando acessar pode, na verdade, estar definida em um object / matriz aninhada.

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

E se os nomes das propriedades forem dynamics e eu não os conheço de antemão?

Se os nomes das propriedades forem desconhecidos ou se quisermos acessar todas as propriedades de um object / elementos de uma matriz, podemos usar o loop for for...in [MDN] para objects e o loop for [MDN] para que as matrizes façam uma iteração sobre todos propriedades / elementos.

Objetos

Para iterar sobre todas as propriedades de data , podemos iterar sobre o object da seguinte forma:

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

Dependendo de onde o object vem (e o que você deseja fazer), talvez seja necessário testar em cada iteração se a propriedade é realmente uma propriedade do object ou se é uma propriedade herdada. Você pode fazer isso com Object#hasOwnProperty [MDN] .

Como alternativa para for...in with hasOwnProperty , você pode usar Object.keys [MDN] para obter uma matriz de nomes de propriedades :

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

Matrizes

Para iterar todos os elementos da matriz data.items , usamos um loop for :

 for(let i = 0, l = data.items.length; i < l; i++) { // `i` will take on the values `0`, `1`, `2`,..., ie 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`. } 

Também é possível usar for...in iterate over arrays, mas existem razões pelas quais isso deve ser evitado: Por que 'for (var item in list)' com arrays consideradas más práticas em JavaScript? .

Com o crescente suporte ao navegador do ECMAScript 5, o método array para o forEach [MDN] se torna uma alternativa interessante também:

 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 (ie `data.items` in this case) }); 

Em ambientes que suportam o ES2015 (ES6), você também pode usar o loop for...of [MDN] , que não funciona apenas para matrizes, mas para qualquer iterável :

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

Em cada iteração, for...of nos fornece diretamente o próximo elemento do iterável, não há "índice" para acessar ou usar.


E se a "profundidade" da estrutura de dados for desconhecida para mim?

Além de chaves desconhecidas, a "profundidade" da estrutura de dados (ou seja, quantos objects nesteds) pode ser desconhecida também. Como acessar propriedades profundamente aninhadas geralmente depende da estrutura de dados exata.

Mas se a estrutura de dados contiver padrões de repetição, por exemplo, a representação de uma tree binária, a solução normalmente inclui a recursividade [Wikipedia] de acessar cada nível da estrutura de dados.

Aqui está um exemplo para obter o primeiro nó da folha de uma tree binária:

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

Você pode acessá-lo dessa maneira

 data.items[1].name 

ou

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

Ambas as formas são iguais.

Caso você esteja tentando acessar um item da estrutura de exemplo por id ou name , sem saber sua posição na matriz, a maneira mais fácil de fazer isso seria usar a biblioteca underscore.js :

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

Pela minha experiência, usar funções de ordem superior em vez de for ou for..in resulta em código mais fácil de raciocinar e, portanto, mais sustentável.

Apenas meus 2 centavos.

Às vezes, o access a um object nested usando uma string pode ser desejável. A abordagem simples é o primeiro nível, por exemplo

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

Mas este não é frequentemente o caso com json complexo. À medida que o json se torna mais complexo, as abordagens para encontrar valores dentro do json também se tornam complexas. Uma abordagem recursiva para navegar no json é melhor, e como essa recursion é aproveitada dependerá do tipo de dados que está sendo pesquisado. Se houver declarações condicionais envolvidas, uma pesquisa json pode ser uma boa ferramenta para usar.

Se a propriedade que está sendo acessada já é conhecida, mas o caminho é complexo, por exemplo, neste object

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

E você sabe que quer obter o primeiro resultado do array no object, talvez você queira usar

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

No entanto, isso causará uma exceção, pois não há propriedade de object com esse nome. A solução para poder usar isso seria nivelar o aspecto da tree do object. Isso pode ser feito 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; } 

Agora, o object complexo pode ser achatado

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

Aqui está um jsFiddle Demo desta abordagem sendo usada.

Objetos e matrizes possuem vários methods integrados que podem ajudá-lo com o processamento de dados.

Nota: em muitos dos exemplos, estou usando as funções de seta . Eles são semelhantes às expressões de function , mas vinculam o valor a this léxico.

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

Object.keys() retorna uma matriz de chaves do object, Object.values() retorna uma matriz de valores do object e Object.entries() retorna uma matriz de chaves do object e valores correspondentes em um 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]] 

Esta questão é bastante antiga, portanto, como uma atualização contemporânea. Com o início do ES2015, há alternativas para se obter os dados de que você precisa. Existe agora um recurso chamado desestruturação de objects para acessar objects nesteds.

 const data = { code: 42, items: [{ id: 1, name: 'foo' }, { id: 2, name: 'bar' }] }; const { items: [, { name: secondName }] } = data; console.log(secondName); 

O uso de JSONPath seria uma das soluções mais flexíveis se você estivesse disposto a include uma biblioteca: https://github.com/s3u/JSONPath (nó e navegador)

Para o seu caso de uso, o caminho json seria:

 $..items[1].name 

assim:

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

Eu prefiro o JQuery. É mais limpo e fácil de ler.

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

Se você está procurando por um ou mais objects que atendem a determinados critérios, você tem algumas opções usando query-js

 //will return all elements with an id larger than 1 data.items.where(function(e){return e.id > 1;}); //will return the first element with an id larger than 1 data.items.first(function(e){return e.id > 1;}); //will return the first element with an id larger than 1 //or the second argument if non are found data.items.first(function(e){return e.id > 1;},{id:-1,name:""}); 

Há também um single e um singleOrDefault que funcionam muito como first e first respectivamente. A única diferença é que eles lançarão se mais de uma partida for encontrada.

para mais explicações sobre query-js você pode começar com este post

Você poderia usar a function lodash _get :

 var object = { 'a': [{ 'b': { 'c': 3 } }] }; _.get(object, 'a[0].b.c'); // => 3 

Para acessar um atributo nested, você precisa especificar seu nome e, em seguida, pesquisar o object.

Se você já conhece o caminho exato, então você pode codificá-lo em seu script da seguinte forma:

 data['items'][1]['name'] 

estes também funcionam

 data.items[1].name data['items'][1].name data.items[1]['name'] 

Quando você não sabe o nome exato antes, ou o usuário é quem fornece o nome para você. Em seguida, é necessário pesquisar dinamicamente a estrutura de dados. Alguns sugeriram aqui que a pesquisa pode ser feita usando um loop for , mas há uma maneira muito simples de percorrer um caminho usando Array.reduce .

 const data = { code: 42, items: [{ id: 1, name: 'foo' }, { id: 2, name: 'bar' }] } const path = [ 'items', '1', 'name'] let result = path.reduce((a,v) => a[v], data) 

O caminho é uma maneira de dizer: Primeiro, pegue o object com items chave, o que é um array. Então pegue o 1 ° elemento (0 matrizes de índice). Por último, pegue o object com o name chave nesse elemento de matriz, que é a bar string.

Se você tem um caminho muito longo, você pode até usar String.split para tornar tudo isso mais fácil –

 'items.1.name'.split('.').reduce((a,v) => a[v], data) 

Este é apenas o JavaScript simples, sem o uso de quaisquer bibliotecas de terceiros, como jQuery ou lodash.

The Underscore js Way

Qual é uma biblioteca JavaScript que fornece toda uma confusão de úteis auxiliares de functional programming sem estender nenhum object interno.

Solução:

 var data = { code: 42, items: [{ id: 1, name: 'foo' }, { id: 2, name: 'bar' }] }; var item = _.findWhere(data.items, { id: 2 }); if (!_.isUndefined(item)) { console.log('NAME =>', item.name); } //using find - var item = _.find(data.items, function(item) { return item.id === 2; }); if (!_.isUndefined(item)) { console.log('NAME =>', item.name); } 

Eu não acho que o questionador só diz respeito a um object nested de nível, então eu apresento a seguinte demonstração para demonstrar como acessar o nó do object json profundamente nested. Tudo bem, vamos encontrar o nó com o id ‘5’.

 var data = { code: 42, items: [{ id: 1, name: 'aaa', items: [{ id: 3, name: 'ccc' }, { id: 4, name: 'ddd' }] }, { id: 2, name: 'bbb', items: [{ id: 5, name: 'eee' }, { id: 6, name: 'fff' }] }] }; var jsonloop = new JSONLoop(data, 'id', 'items'); jsonloop.findNodeById(data, 5, function(err, node) { if (err) { document.write(err); } else { document.write(JSON.stringify(node, null, 2)); } }); 
  

Pergunta antiga, mas ninguém mencionou lodash (apenas sublinhado).

Caso você já esteja usando o lodash em seu projeto, eu acho uma maneira elegante de fazer isso em um exemplo complexo:

Opte 1

 _.get(response, ['output', 'fund', 'data', '0', 'children', '0', 'group', 'myValue'], '') 

igual a:

Opt 2

 response.output.fund.data[0].children[0].group.myValue 

A diferença entre a primeira e a segunda opção é que no Opt 1, se você tiver uma das propriedades faltando (indefinida) no caminho, você não recebe um erro, ele retorna o terceiro parâmetro.

Para o filtro de matriz, o lodash tem _.find() mas prefiro usar o filter() regular filter() . Mas eu ainda acho que o método acima _.get() é super útil quando se trabalha com dados realmente complexos. Eu enfrentei no passado APIs realmente complexas e foi útil!

Espero que possa ser útil para quem está procurando opções para manipular dados realmente complexos que o título implica.

Acessando dinamicamente vários níveis de object.

 var obj = { name: "salut", subobj: { subsubobj: { names: "I am sub sub obj" } } }; var level = "subobj.subsubobj.names"; level = level.split("."); var currentObjState = obj; for (var i = 0; i < level.length; i++) { currentObjState = currentObjState[level[i]]; } console.log(currentObjState); 

Violino de trabalho: https://jsfiddle.net/andreitodorut/3mws3kjL/

Uma abordagem pythonic, recursiva e funcional para desvendar trees JSON arbitrárias:

 handlers = { list: iterate, dict: delve, str: emit_li, float: emit_li, } def emit_li(stuff, strong=False): emission = '
  • %s
  • ' if strong else '
  • %s
  • ' print(emission % stuff) def iterate(a_list): print('
      ') map(unravel, a_list) print('
    ') def delve(a_dict): print('
      ') for key, value in a_dict.items(): emit_li(key, strong=True) unravel(value) print('
    ') def unravel(structure): h = handlers[type(structure)] return h(structure) unravel(data)

    em que data é uma lista de python (analisada a partir de uma string de texto JSON):

     data = [ {'data': {'customKey1': 'customValue1', 'customKey2': {'customSubKey1': {'customSubSubKey1': 'keyvalue'}}}, 'geometry': {'location': {'lat': 37.3860517, 'lng': -122.0838511}, 'viewport': {'northeast': {'lat': 37.4508789, 'lng': -122.0446721}, 'southwest': {'lat': 37.3567599, 'lng': -122.1178619}}}, 'name': 'Mountain View', 'scope': 'GOOGLE', 'types': ['locality', 'political']} ] 

    A function grep do jQuery permite filtrar por um array:

     var data = { code: 42, items: [{ id: 1, name: 'foo' }, { id: 2, name: 'bar' }] }; $.grep(data.items, function(item) { if (item.id === 2) { console.log(item.id); //console id of item console.log(item.name); //console name of item console.log(item); //console item object return item; //returns item object } }); // Object {id: 2, name: "bar"} 
      

    Apenas no caso, alguém está visitando esta questão em 2017 ou mais tarde e procurando uma maneira fácil de lembrar , aqui está uma elaborada postagem no blog sobre como acessar objects nesteds em JavaScript sem ser enganada por

    Não é possível ler a propriedade ‘foo’ de erro indefinido

    1. Padrão de access a objects nesteds de Oliver Steele

    A maneira mais fácil e mais limpa é usar o padrão de access a objects nesteds de Oliver Steele.

     const name = ((user || {}).personalInfo || {}).name; 

    Com essa notação, você nunca vai se deparar

    Não é possível ler a propriedade ‘name’ de indefinido .

    Você basicamente verifica se o usuário existe, se não, você cria um object vazio na hora. Desta forma, a próxima chave de nível será sempre acessada de um object que existe ou de um object vazio , mas nunca de um object indefinido.

    2. Acessar Objetos Aninhados Usando Redução de Matriz

    Para poder acessar matrizes aninhadas, você pode escrever sua própria matriz, reduzir util.

     const getNestedObject = (nestedObj, pathArr) => { return pathArr.reduce((obj, key) => (obj && obj[key] !== 'undefined') ? obj[key] : undefined, nestedObj); } // pass in your object structure as array elements const name = getNestedObject(user, ['personalInfo', 'name']); // to access nested array, just pass in array index as an element the path array. const city = getNestedObject(user, ['personalInfo', 'addresses', 0, 'city']); // this will return the city from the first address item. 

    Há também um excelente tipo de manipulação de biblioteca mínima que faz tudo isso para você.

    Usando o lodash seria uma boa solução

    Ex:

     var object = { 'a': { 'b': { 'c': 3 } } }; _.get(object, 'abc'); // => 3 
     var ourStorage = { "desk": { "drawer": "stapler" }, "cabinet": { "top drawer": { "folder1": "a file", "folder2": "secrets" }, "bottom drawer": "soda" } }; ourStorage.cabinet["top drawer"].folder2; // Outputs -> "secrets" 

    ou

     //parent.subParent.subsubParent["almost there"]["final property"] 

    Basicamente, use um ponto entre cada descendente que se desdobra embaixo dele e quando você tiver nomes de objects feitos de duas strings, você deve usar a notação [“obj Name”]. Caso contrário, apenas um ponto seria suficiente;

    Fonte: https://learn.freecodecamp.org/javascript-algorithms-and-data-structures/basic-javascript/accessing-nested-objects

    Para adicionar isso, o access a matrizes aninhadas seria assim:

     var ourPets = [ { animalType: "cat", names: [ "Meowzer", "Fluffy", "Kit-Cat" ] }, { animalType: "dog", names: [ "Spot", "Bowser", "Frankie" ] } ]; ourPets[0].names[1]; // Outputs "Fluffy" ourPets[1].names[0]; // Outputs "Spot" 

    Fonte: https://learn.freecodecamp.org/javascript-algorithms-and-data-structures/basic-javascript/accessing-nested-arrays/