javascript - parse - typescript access nested property by string




Accesso/processo(nidificato) oggetti, matrici o JSON (14)

Ho una struttura dati annidata contenente oggetti e matrici. Come posso estrarre l'informazione, cioè accedere a uno specifico o più valori (o chiavi)?

Per esempio:

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

Come posso accedere al name del secondo oggetto negli items ?


Il modo Underscore js

Che è una libreria JavaScript che fornisce un intero casino di utili aiutanti di functional programming senza estendere alcun oggetto incorporato.

Soluzione:

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

Preliminari

JavaScript ha un solo tipo di dati che può contenere più valori: Object . Una matrice è una forma speciale di oggetto.

(Semplice) Gli oggetti hanno la forma

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

Gli array hanno la forma

[value, value, ...]

Sia gli array che gli oggetti espongono una key -> value struttura del key -> value . Le chiavi di un array devono essere numeriche, mentre qualsiasi stringa può essere utilizzata come chiave negli oggetti. Le coppie chiave-valore sono anche chiamate "proprietà" .

È possibile accedere alle proprietà utilizzando la notazione a punti

const value = obj.someProperty;

o notazione parentesi , se il nome della proprietà non sarebbe un nome identificatore JavaScript valido [specifica] , o il nome è il valore di una variabile:

// 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];

Per questo motivo, è possibile accedere agli elementi dell'array usando la notazione della parentesi:

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

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

Aspetta ... e JSON?

JSON è una rappresentazione testuale di dati, proprio come XML, YAML, CSV e altri. Per lavorare con tali dati, è necessario prima convertirli in tipi di dati JavaScript, ad esempio matrici e oggetti (e come è stato spiegato come lavorarli). Come analizzare JSON è spiegato nella domanda Parse JSON in JavaScript? .

Ulteriore materiale di lettura

Come accedere a matrici e oggetti è fondamentale conoscenza di JavaScript e quindi è consigliabile leggere la Guida JavaScript MDN , in particolare le sezioni

Accesso a strutture dati nidificate

Una struttura di dati nidificata è una matrice o un oggetto che fa riferimento ad altri matrici o oggetti, vale a dire che i suoi valori sono matrici o oggetti. È possibile accedere a tali strutture applicando consecutivamente la notazione dot o parentesi.

Ecco un esempio:

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

Supponiamo di voler accedere al name del secondo elemento.

Ecco come possiamo farlo passo-passo:

Come possiamo vedere i data è un oggetto, quindi possiamo accedere alle sue proprietà usando la notazione a punti. La proprietà items è accessibile come segue:

data.items

Il valore è un array, per accedere al suo secondo elemento, dobbiamo usare la notazione delle parentesi:

data.items[1]

Questo valore è un oggetto e utilizziamo nuovamente la notazione a punti per accedere alla proprietà name . Quindi alla fine otteniamo:

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

In alternativa, avremmo potuto usare la notazione delle parentesi per qualsiasi proprietà, specialmente se il nome contenesse caratteri che avrebbero reso invalido l'uso della notazione di punti:

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

Sto provando ad accedere a una proprietà, ma mi rendo solo undefined ?

La maggior parte delle volte quando stai diventando undefined , l'oggetto / array semplicemente non ha una proprietà con quel nome.

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

Utilizzare console.log o console.dir e ispezionare la struttura di object / array. La proprietà a cui stai tentando di accedere potrebbe essere effettivamente definita su un oggetto / array nidificato.

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

Cosa succede se i nomi delle proprietà sono dinamici e non li conosco in anticipo?

Se i nomi delle proprietà sono sconosciuti o vogliamo accedere a tutte le proprietà di un oggetto / elementi di un array, possiamo usare for...in ciclo [MDN] per gli oggetti e il ciclo for [MDN] per gli array per iterare su tutti proprietà / elementi.

Oggetti

Per scorrere tutte le proprietà dei data , possiamo scorrere l' oggetto in questo modo:

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
}

A seconda di da dove proviene l'oggetto (e cosa si vuole fare), potrebbe essere necessario testare in ogni iterazione se la proprietà è realmente una proprietà dell'oggetto, oppure è una proprietà ereditata. Puoi farlo con Object#hasOwnProperty [MDN] .

In alternativa a for...in con hasOwnProperty , puoi usare Object.keys [MDN] per ottenere una matrice di nomi di proprietà :

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

Array

Per data.items tutti gli elementi dell'array data.items , utilizziamo un ciclo for :

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

Si potrebbe anche usare for...in per iterare su array, ma ci sono ragioni per cui questo dovrebbe essere evitato: perché 'per (var item in list)' con array considerati cattivi esercizi in JavaScript? .

Con l'aumento del supporto per browser di ECMAScript 5, anche il metodo array forEach [MDN] diventa un'alternativa interessante:

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

In ambienti che supportano ES2015 (ES6), è anche possibile utilizzare il ciclo for...of [MDN] , che non funziona solo per gli array, ma per qualsiasi iterable :

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

In ogni iterazione, for...of direttamente ci dà il prossimo elemento del iterable, non c'è alcun "indice" per accedere o utilizzare.

Cosa succede se la "profondità" della struttura dei dati mi è sconosciuta?

Oltre alle chiavi sconosciute, la "profondità" della struttura dei dati (cioè quanti oggetti nidificati) ha, potrebbe anche essere sconosciuta. Come accedere a proprietà profondamente annidate solitamente dipende dalla struttura esatta dei dati.

Ma se la struttura dei dati contiene pattern ripetuti, ad esempio la rappresentazione di un albero binario, la soluzione include in genere ricorsivamente [Wikipedia] accedere a ciascun livello della struttura dei dati.

Ecco un esempio per ottenere il primo nodo foglia di un albero 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);

Un modo più generico per accedere a una struttura dati nidificata con chiavi e profondità sconosciute è testare il tipo di valore e agire di conseguenza.

Ecco un esempio che aggiunge tutti i valori primitivi all'interno di una struttura di dati nidificata in una matrice (supponendo che non contenga alcuna funzione). Se incontriamo un oggetto (o un array), chiamiamo nuovamente toArray su quel valore (chiamata ricorsiva).

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

Helpers

Poiché la struttura di un oggetto o di una matrice complessi non è necessariamente ovvia, possiamo esaminare il valore di ogni fase per decidere come procedere ulteriormente. console.log e console.dir ci aiutano a farlo. Ad esempio (output della console di Chrome):

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

Qui vediamo che data.items è un array con due elementi che sono entrambi gli oggetti. Nella console di Chrome gli oggetti possono anche essere espansi e controllati immediatamente.

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

Questo ci dice che data.items[1] è un oggetto, e dopo averlo espanso vediamo che ha tre proprietà, id , name e __proto__ . Quest'ultima è una proprietà interna utilizzata per la catena prototipo dell'oggetto. La catena del prototipo e l'ereditarietà sono fuori dallo scopo di questa risposta, però.


Accesso a oggetti a più livelli dinamici.

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

Working fiddle: https://jsfiddle.net/andreitodorut/3mws3kjL/


Nel caso in cui qualcuno visiti questa domanda nel 2017 o in seguito e cerchi un modo facile da ricordare , ecco un elaborato post sul blog Accesso agli oggetti nidificati in JavaScript senza essere imbrogliato da

Impossibile leggere la proprietà "pippo" di errore non definito

1. Schema di accesso ad oggetti nidificati di Oliver Steele

Il modo più semplice e più pulito è utilizzare il pattern di accesso agli oggetti nidificati di Oliver Steele

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

Con questa notazione, non incontrerai mai

Impossibile leggere il nome della proprietà di indefinito .

Fondamentalmente si controlla se l'utente esiste, in caso contrario, si crea un oggetto vuoto al volo. In questo modo, la chiave di livello successivo sarà sempre accessibile da un oggetto esistente o da un oggetto vuoto , ma mai da undefined.

2. Accedere agli oggetti nidificati utilizzando la matrice Riduci

Per poter accedere agli array annidati, è possibile scrivere il proprio array per ridurre l'utilità.

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.

C'è anche un eccellente tipo che gestisce una libreria typy che fa tutto questo per te.


Non penso che il questionario riguardi solo un oggetto nidificato a un livello, quindi presento il seguente demo per dimostrare come accedere al nodo dell'oggetto json profondamente annidato. Bene, troviamo il nodo con 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));
  }
});
<script src="https://rawgit.com/dabeng/JSON-Loop/master/JSONLoop.js"></script>


Oggetti e matrici hanno molti metodi integrati che possono aiutarti nell'elaborazione dei dati.

Nota: in molti degli esempi utilizzo le funzioni freccia . Sono simili alle espressioni di funzione , ma legano lessicamente this valore.

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

Object.keys() restituisce una matrice di chiavi dell'oggetto, Object.values() restituisce una matrice di valori dell'oggetto e Object.entries() restituisce una matrice di chiavi dell'oggetto e valori corrispondenti in 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 ciclo for-of e un compito destrutturante

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

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

È molto conveniente iterare il risultato di Object.entries() con un ciclo for-of e un compito di destrutturazione .

Il ciclo For-of consente di iterare gli elementi dell'array. La sintassi è for (const element of array) (possiamo sostituire const con var o let , ma è meglio usare const se non intendiamo modificare l' element ).

L'assegnazione alla distruzione consente di estrarre i valori da una matrice o da un oggetto e assegnarli alle variabili. In questo caso const [key, value] significa che invece di assegnare la matrice [key, value] element , assegniamo il primo elemento di quell'array alla key e il secondo elemento da value . È equivalente a questo:

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

Come puoi vedere, la destrutturazione lo rende molto più semplice.

Array.prototype.every() e Array.prototype.some()

Il metodo every() restituisce true se la funzione di callback specificata restituisce true per ogni elemento dell'array. Il metodo some() restituisce true se la funzione di callback specificata restituisce true per alcuni (almeno uno) 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() e Array.prototype.filter()

I metodi find() restituiscono il primo elemento che soddisfa la funzione di callback fornita. Il metodo filter() restituisce una matrice di tutti gli elementi che soddisfa la funzione di callback fornita.

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

Il metodo map() restituisce una matrice con i risultati della chiamata di una funzione di callback fornita sugli elementi dell'array.

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

Il metodo reduce reduce() riduce una matrice a un singolo valore chiamando la funzione di callback fornita con due elementi.

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

Il metodo reduce() accetta un secondo parametro opzionale, che è il valore iniziale. Questo è utile quando l'array su cui si chiama reduce() può avere zero o un elemento. Ad esempio, se volessimo creare una funzione sum() che accetta un array come argomento e restituisce la somma di tutti gli elementi, potremmo scrivere in questo modo:

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


Potresti usare la funzione lodash _get :

var object = { 'a': [{ 'b': { 'c': 3 } }] };

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

Preferisco JQuery. È più pulito e facile da leggere.

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


Questa domanda è abbastanza vecchia, così come un aggiornamento contemporaneo. Con l'inizio di ES2015 ci sono alternative per ottenere una sospensione dei dati richiesti. Esiste ora una funzionalità chiamata destrutturazione di oggetti per l'accesso agli oggetti nidificati.

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

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

console.log(secondName);

L'esempio precedente crea una variabile chiamata secondName dalla chiave del name da un array chiamato items , the lonely , dice salta il primo oggetto nella matrice.

In particolare è probabilmente eccessivo per questo esempio, poiché la semplice matrice di accesso è più facile da leggere, ma è utile quando si rompono gli oggetti in generale.

Questa è un'introduzione molto breve al tuo caso d'uso specifico, la destrutturazione può essere una sintassi insolita su cui abituarsi all'inizio. Consiglierei di leggere la documentazione sull'assegnazione delle distruzioni di Mozilla per saperne di più.


Se stai cercando uno o più oggetti che soddisfano determinati criteri hai alcune opzioni 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:""});

C'è anche un singleOrDefault single e uno singleOrDefault che funzionano come first e firstOrDefault rispettivamente. L'unica differenza è che verranno lanciati se viene trovata più di una corrispondenza.

per ulteriori spiegazioni su query-js puoi iniziare con questo post


Utilizzare JSONPath sarebbe una delle soluzioni più flessibili se si desidera includere una libreria: https://github.com/s3u/JSONPath (nodo e browser)

Per il tuo caso d'uso il percorso JSON sarebbe:

$..items[1].name

così:

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

Vecchia domanda ma come nessuno ha menzionato lodash (solo sottolineatura).

Nel caso tu stia già usando lodash nel tuo progetto, penso che un modo elegante per farlo in un esempio complesso:

Opz 1

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

uguale a:

Opt 2

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

La differenza tra la prima e la seconda opzione è che nell'opzione 1 se si ha una delle proprietà mancanti (non definita) nel percorso non si ottiene un errore, si ritorna al terzo parametro.

Per il filtro dell'array, il lodash ha _.find() ma preferisco usare il filter() normale filter() . Ma continuo a pensare che il metodo precedente _.get() sia molto utile quando si lavora con dati veramente complessi. Ho affrontato in passato API molto complesse ed è stato utile!

Spero che possa essere utile per chi cerca opzioni per manipolare dati davvero complessi che il titolo implica.


L'uso di lodash sarebbe una buona soluzione

Ex:

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

var ourStorage = {


"desk": {
    "drawer": "stapler"
  },
  "cabinet": {
    "top drawer": { 
      "folder1": "a file",
      "folder2": "secrets"
    },
    "bottom drawer": "soda"
  }
};
ourStorage.cabinet["top drawer"].folder2; // Outputs -> "secrets"

o

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

In pratica, usa un punto tra ciascun discendente che si apre al di sotto di esso e quando hai nomi di oggetti composti da due stringhe, devi usare la notazione ["obj Name"]. Altrimenti, basterebbe un solo punto;

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

per aggiungere a questo, l'accesso a matrici annidate avverrebbe in questo modo:

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/





data-manipulation