Accesso agli oggetti JavaScript nidificati con chiave di stringa

Ho una struttura dati come questa:

var someObject = { 'part1' : { 'name': 'Part 1', 'size': '20', 'qty' : '50' }, 'part2' : { 'name': 'Part 2', 'size': '15', 'qty' : '60' }, 'part3' : [ { 'name': 'Part 3A', 'size': '10', 'qty' : '20' }, { 'name': 'Part 3B', 'size': '5', 'qty' : '20' }, { 'name': 'Part 3C', 'size': '7.5', 'qty' : '20' } ] }; 

E vorrei accedere ai dati usando queste variabili:

 var part1name = "part1.name"; var part2quantity = "part2.qty"; var part3name1 = "part3[0].name"; 

part1name dovrebbe essere riempito con il valore someObject.part1.name , che è “Part 1”. Stessa cosa con part2quantity che ha riempito con 60.

C’è comunque un modo per raggiungere questo objective con javascript puro o JQuery?

L’ho appena creato basandomi su un codice simile che avevo già, sembra funzionare:

 Object.byString = function(o, s) { s = s.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties s = s.replace(/^\./, ''); // strip a leading dot var a = s.split('.'); for (var i = 0, n = a.length; i < n; ++i) { var k = a[i]; if (k in o) { o = o[k]; } else { return; } } return o; } 

Uso ::

 Object.byString(someObj, 'part3[0].name'); 

Guarda una demo funzionante su http://jsfiddle.net/alnitak/hEsys/

EDIT alcuni hanno notato che questo codice genererà un errore se ha passato una stringa in cui gli indici più a sinistra non corrispondono ad una voce annidata correttamente all'interno dell'object. Questa è una preoccupazione valida, ma IMHO si indirizza meglio con un blocco try / catch quando si chiama, piuttosto che avere questa funzione restituisce automaticamente undefined per un indice non valido.

Questa è la soluzione che utilizzo:

 function resolve(path, obj=self, separator='.') { var properties = Array.isArray(path) ? path : path.split(separator) return properties.reduce((prev, curr) => prev && prev[curr], obj) } 

Esempio di utilizzo:

 // accessing property path on global scope resolve("document.body.style.width") // or resolve("style.width", document.body) // accessing array indexes // (someObject has been defined in the question) resolve("part3.0.size", someObject) // returns '10' // accessing non-existent properties // returns undefined when intermediate properties are not defined: resolve('properties.that.do.not.exist', {hello:'world'}) // accessing properties with unusual keys by changing the separator var obj = { object: { 'a.property.name.with.periods': 42 } } resolve('object->a.property.name.with.periods', obj, '->') // returns 42 // accessing properties with unusual keys by passing a property name array resolve(['object', 'a.property.name.with.periods'], obj) // returns 42 

limitazioni:

  • Non è ansible utilizzare parentesi ( [] ) per gli indici di matrice, sebbene la specifica degli indici di matrice tra il token separatore (ad esempio,.) Funzioni correttamente come mostrato sopra.

Questo è ora supportato da lodash usando _.get(obj, property) . Vedi https://lodash.com/docs#get

Esempio dai documenti:

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

Dovresti analizzare la stringa tu stesso:

 function getProperty(obj, prop) { var parts = prop.split('.'); if (Array.isArray(parts)) { var last = parts.pop(), l = parts.length, i = 1, current = parts[0]; while((obj = obj[current]) && i < l) { current = parts[i]; i++; } if(obj) { return obj[last]; } } else { throw 'parts is not valid array'; } } 

Ciò ha richiesto di definire anche gli indici di array con notazione dei punti:

 var part3name1 = "part3.0.name"; 

Semplifica l'analisi.

DEMO

Funziona anche per gli array / array all’interno dell’object. Difensivo contro valori non validi.

 /** * Retrieve nested item from object/array * @param {Object|Array} obj * @param {String} path dot separated * @param {*} def default value ( if result undefined ) * @returns {*} */ function path(obj, path, def){ var i, len; for(i = 0,path = path.split('.'), len = path.length; i < len; i++){ if(!obj || typeof obj !== 'object') return def; obj = obj[path[i]]; } if(obj === undefined) return def; return obj; } ////////////////////////// // TEST // ////////////////////////// var arr = [true, {'sp ace': true}, true] var obj = { 'sp ace': true, arr: arr, nested: {'dotted.str.ing': true}, arr3: arr } shouldThrow(`path(obj, "arr.0")`); shouldBeDefined(`path(obj, "arr[0]")`); shouldBeEqualToNumber(`path(obj, "arr.length")`, 3); shouldBeTrue(`path(obj, "sp ace")`); shouldBeEqualToString(`path(obj, "none.existed.prop", "fallback")`, "fallback"); shouldBeTrue(`path(obj, "nested['dotted.str.ing'])`); 
  

ES6 : Solo una riga in Vanila JS (restituisce null se non trova invece di dare errore):

 'path.string'.split('.').reduce((p,c)=>p&&p[c]||null, MyOBJ) 

o esempio:

 'abc'.split('.').reduce((p,c)=>p&&p[c]||null, {a:{b:{c:1}}}) 

Per una funzione pronta all’uso che riconosce anche il numero falso, 0 e negativo e accetta come valori i valori predefiniti:

 const resolvePath = (object, path, defaultValue) => path .split('.') .reduce((o, p) => o ? o[p] : defaultValue, object) 

Esemplare da usare:

 resolvePath(window,'document.body') =>  resolvePath(window,'document.body.xyz') => undefined resolvePath(window,'document.body.xyz', null) => null resolvePath(window,'document.body.xyz', 1) => 1 

Bonus :

Per impostare un percorso (richiesto da @ rob-gordon) puoi utilizzare:

 const setPath = (object, path, value) => path .split('.') .reduce((o,p) => o[p] = path.split('.').pop() === p ? value : o[p] || {}, object) 

Esempio:

 let myVar = {} setPath(myVar, 'abc', 42) => 42 console.log(myVar) => {a: {b: {c: 42}}} 

Accedi alla matrice con [] :

 const resolvePath = (object, path, defaultValue) => path .split(/[\.\[\]\'\"]/) .filter(p => p) .reduce((o, p) => o ? o[p] : defaultValue, object) 

exemple

 const myVar = {a:{b:[{c:1}]}} resolvePath(myVar,'ab[0].c') => 1 resolvePath(myVar,'a["b"][\'0\'].c') => 1 

usando eval:

 var part1name = eval("someObject.part1.name"); 

avvolgere per restituire un errore non definito

 function path(obj, path) { try { return eval("obj." + path); } catch(e) { return undefined; } } 

http://jsfiddle.net/shanimal/b3xTw/

Si prega di usare il buon senso e la caucanvas quando si esercita il potere di valutazione. È un po ‘come una sciabola leggera, se la accendi c’è una probabilità del 90% di recidere un arto. Non è per tutti.

È ansible ottenere il valore di un membro dell’object profondo con notazione punto senza alcuna libreria JavaScript esterna con il seguente semplice trucco:

 new Function('_', 'return _.' + path)(obj); 

Nel tuo caso per ottenere il valore di part1.name da someObject devi solo fare:

 new Function('_', 'return _.part1.name')(someObject); 

Ecco una semplice demo di fiddle: https://jsfiddle.net/harishanchu/oq5esowf/

Penso che tu stia chiedendo questo:

 var part1name = someObject.part1.name; var part2quantity = someObject.part2.qty; var part3name1 = someObject.part3[0].name; 

Potresti chiedere questo:

 var part1name = someObject["part1"]["name"]; var part2quantity = someObject["part2"]["qty"]; var part3name1 = someObject["part3"][0]["name"]; 

Entrambe funzioneranno


O forse lo stai chiedendo

 var partName = "part1"; var nameStr = "name"; var part1name = someObject[partName][nameStr]; 

Finalmente potresti chiederlo

 var partName = "part1.name"; var partBits = partName.split("."); var part1name = someObject[partBits[0]][partBits[1]]; 

Qui offro altri modi, che sembrano più rapidi sotto molti aspetti:

Opzione 1: Dividi la stringa. oppure [o] o “o”, invertendolo, salta gli oggetti vuoti.

 function getValue(path, origin) { if (origin === void 0 || origin === null) origin = self ? self : this; if (typeof path !== 'string') path = '' + path; var parts = path.split(/\[|\]|\.|'|"/g).reverse(), name; // (why reverse? because it's usually faster to pop off the end of an array) while (parts.length) { name=parts.pop(); if (name) origin=origin[name]; } return origin; } 

Opzione 2 (il più veloce di tutti, eccetto eval ): scansione dei caratteri di basso livello (nessuna regex / split / etc, solo una rapida scansione dei caratteri). Nota: questo non supporta le virgolette per gli indici.

 function getValue(path, origin) { if (origin === void 0 || origin === null) origin = self ? self : this; if (typeof path !== 'string') path = '' + path; var c = '', pc, i = 0, n = path.length, name = ''; if (n) while (i< =n) ((c = path[i++]) == '.' || c == '[' || c == ']' || c == void 0) ? (name?(origin = origin[name], name = ''):(pc=='.'||pc=='['||pc==']'&&c==']'?i=n+2:void 0),pc=c) : name += c; if (i==n+2) throw "Invalid path: "+path; return origin; } // (around 1,000,000+/- ops/sec) 

Opzione 3: ( nuovo : opzione 2 espansa per supportare le virgolette - un po 'più lento, ma comunque veloce)

 function getValue(path, origin) { if (origin === void 0 || origin === null) origin = self ? self : this; if (typeof path !== 'string') path = '' + path; var c, pc, i = 0, n = path.length, name = '', q; while (i< =n) ((c = path[i++]) == '.' || c == '[' || c == ']' || c == "'" || c == '"' || c == void 0) ? (c==q&&path[i]==']'?q='':q?name+=c:name?(origin?origin=origin[name]:i=n+2,name='') : (pc=='['&&(c=='"'||c=="'")?q=c:pc=='.'||pc=='['||pc==']'&&c==']'||pc=='"'||pc=="'"?i=n+2:void 0), pc=c) : name += c; if (i==n+2 || name) throw "Invalid path: "+path; return origin; } 

JSPerf: http://jsperf.com/ways-to-dereference-a-delimited-property-string/3

"eval (...)" è ancora il re però (le prestazioni sono così). Se hai percorsi di proprietà direttamente sotto il tuo controllo, non ci dovrebbero essere problemi con l'uso di 'eval' (specialmente se la velocità è desiderata). Se si tracciano percorsi di proprietà "sul filo" ( sulla linea !? Lol: P), allora sì, usare qualcos'altro per essere al sicuro. Solo un idiota direbbe di non usare mai "eval", perché ci sono buone ragioni per usarlo. Inoltre, "È usato nel parser JSON di Doug Crockford ." Se l'input è sicuro, quindi nessun problema. Usa lo strumento giusto per il lavoro giusto, il gioco è fatto.

L’approccio di Speigg è molto accurato e pulito, anche se ho trovato questa risposta mentre cercavo la soluzione per accedere alle proprietà dell’angular di AngularJS $ tramite il percorso delle stringhe e con una piccola modifica che esegue il lavoro:

 $scope.resolve = function( path, obj ) { return path.split('.').reduce( function( prev, curr ) { return prev[curr]; }, obj || this ); } 

Basta posizionare questa funzione nel tuo controller di root e usarla come scope secondario come questa:

 $scope.resolve( 'path.to.any.object.in.scope') 

È una nave con lodash.

 const deep = { l1: { l2: { l3: "Hello" } } }; const prop = "l1.l2.l3"; const val = _.reduce(prop.split('.'), function(result, value) { return result ? result[value] : undefined; }, deep); // val === "Hello" 

O ancora meglio …

 const val = _.get(deep, prop); 

O versione ES6 con riduzione …

 const val = prop.split('.').reduce((r, val) => { return r ? r[val] : undefined; }, deep); 

Plunkr

Non ho ancora trovato un pacchetto per fare tutte le operazioni con un percorso di stringa, quindi ho finito per scrivere il mio piccolo pacchetto rapido che supporta insert (), get () (con ritorno predefinito), set () e remove ( ) operazioni.

È ansible utilizzare notazione a punti, parentesi quadre, indici numerici, proprietà del numero di stringa e tasti con caratteri non di parole. Semplice utilizzo di seguito:

 > var jsocrud = require('jsocrud'); ... // Get (Read) --- > var obj = { > foo: [ > { > 'key w/ non-word chars': 'bar' > } > ] > }; undefined > jsocrud.get(obj, '.foo[0]["key w/ non-word chars"]'); 'bar' 

https://www.npmjs.com/package/jsocrud

https://github.com/vertical-knowledge/jsocrud

Funzione semplice, che consente il tracciamento di una stringa o di un array.

 function get(obj, path) { if(typeof path === 'string') path = path.split('.'); if(path.length === 0) return obj; return get(obj[path[0]], path.slice(1)); } const obj = {a: {b: {c: 'foo'}}}; console.log(get(obj, 'abc')); //foo 

O

 console.log(get(obj, ['a', 'b', 'c'])); //foo 

C’è un modulo npm ora per fare questo: https://github.com/erictrinh/safe-access

Esempio di utilizzo:

 var access = require('safe-access'); access(very, 'nested.property.and.array[0]'); 
 /** * Access a deep value inside a object * Works by passing a path like "foo.bar", also works with nested arrays like "foo[0][1].baz" * @author Victor B. https://gist.github.com/victornpb/4c7882c1b9d36292308e * Unit tests: http://jsfiddle.net/Victornpb/0u1qygrh/ */ function getDeepVal(obj, path) { if (typeof obj === "undefined" || obj === null) return; path = path.split(/[\.\[\]\"\']{1,2}/); for (var i = 0, l = path.length; i < l; i++) { if (path[i] === "") continue; obj = obj[path[i]]; if (typeof obj === "undefined" || obj === null) return; } return obj; } 

Lavora con

 getDeepVal(obj,'foo.bar') getDeepVal(obj,'foo.1.bar') getDeepVal(obj,'foo[0].baz') getDeepVal(obj,'foo[1][2]') getDeepVal(obj,"foo['bar'].baz") getDeepVal(obj,"foo['bar']['baz']") getDeepVal(obj,"foo.bar.0.baz[1]['2']['w'].aaa[\"f\"].bb") 

Questo probabilmente non vedrà mai la luce del giorno … ma qui è comunque.

  1. Sostituire la syntax della parentesi [] con .
  2. Dividi sul personaggio
  3. Trova il percorso (altrimenti undefined )
 // "one liner" (ES6) const deep_value = (obj, path) => path .replace(/\[|\]\.?/g, '.') .split('.') .reduce((acc, val) => acc && acc[val], obj); // ... and that's it. var someObject = { 'part1' : { 'name': 'Part 1', 'size': '20', 'qty' : '50' }, 'part2' : { 'name': 'Part 2', 'size': '15', 'qty' : '60' }, 'part3' : [ { 'name': 'Part 3A', 'size': '10', 'qty' : '20' } // ... ] }; console.log(deep_value(someObject, "part1.name")); // Part 1 console.log(deep_value(someObject, "part2.qty")); // 60 console.log(deep_value(someObject, "part3[0].name")); // Part 3A 

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

Imansible leggere la proprietà “pippo” di errore non definito

Accedi agli oggetti nidificati usando la matrice Riduci

Prendiamo questa struttura di esempio

 const user = { id: 101, email: 'jack@dev.com', personalInfo: { name: 'Jack', address: [{ line1: 'westwish st', line2: 'washmasher', city: 'wallas', state: 'WX' }] } } 

Per poter accedere agli array annidati, è ansible 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', 'address', 0, 'city']); // this will return the city from the first address item. 

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

Con typy, il tuo codice sarà simile a questo

 const city = t(user, 'personalInfo.address[0].city').safeObject; 

Disclaimer: sono l’autore di questo pacchetto.

Se è necessario accedere a una chiave annidata diversa senza conoscerla al momento della codifica (sarà banale indirizzarle) è ansible utilizzare la funzione di accesso alla notazione array:

 var part1name = someObject['part1']['name']; var part2quantity = someObject['part2']['qty']; var part3name1 = someObject['part3'][0]['name']; 

Sono equivalenti all’accessorio di notazione dot e possono variare in fase di esecuzione, ad esempio:

 var part = 'part1'; var property = 'name'; var part1name = someObject[part][property]; 

è equivalente a

 var part1name = someObject['part1']['name']; 

o

 var part1name = someObject.part1.name; 

Spero che questo indirizzo la tua domanda …

MODIFICARE

Non userò una stringa per mantenere una sorta di query xpath per accedere a un valore dell’object. Come si deve chiamare una funzione per analizzare la query e recuperare il valore, vorrei seguire un altro percorso (non:

 var part1name = function(){ return this.part1.name; } var part2quantity = function() { return this['part2']['qty']; } var part3name1 = function() { return this.part3[0]['name'];} // usage: part1name.apply(someObject); 

o, se sei a disagio con il metodo apply

 var part1name = function(obj){ return obj.part1.name; } var part2quantity = function(obj) { return obj['part2']['qty']; } var part3name1 = function(obj) { return obj.part3[0]['name'];} // usage: part1name(someObject); 

Le funzioni sono più brevi, più chiare, l’interprete li controlla per gli errori di syntax e così via.

A proposito, sento che un semplice compito fatto al momento giusto sarà sufficiente …

Ho appena avuto la stessa domanda di recente e utilizzato con successo https://npmjs.org/package/tea-properties che ha anche set oggetti / matrici annidate:

ottenere:

 var o = { prop: { arr: [ {foo: 'bar'} ] } }; var properties = require('tea-properties'); var value = properties.get(o, 'prop.arr[0].foo'); assert(value, 'bar'); // true 

impostato:

 var o = {}; var properties = require('tea-properties'); properties.set(o, 'prop.arr[0].foo', 'bar'); assert(o.prop.arr[0].foo, 'bar'); // true 

Le soluzioni qui sono solo per l’accesso alle chiavi profondamente annidate. Ne avevo bisogno uno per accedere, aggiungere, modificare e cancellare le chiavi. Questo è quello che mi è venuto in mente:

 var deepAccessObject = function(object, path_to_key, type_of_function, value){ switch(type_of_function){ //Add key/modify key case 0: if(path_to_key.length === 1){ if(value) object[path_to_key[0]] = value; return object[path_to_key[0]]; }else{ if(object[path_to_key[0]]) return deepAccessObject(object[path_to_key[0]], path_to_key.slice(1), type_of_function, value); else object[path_to_key[0]] = {}; } break; //delete key case 1: if(path_to_key.length === 1){ delete object[path_to_key[0]]; return true; }else{ if(object[path_to_key[0]]) return deepAccessObject(object[path_to_key[0]], path_to_key.slice(1), type_of_function, value); else return false; } break; default: console.log("Wrong type of function"); } }; 
  • path_to_key : percorso in una matrice. Puoi sostituirlo con il tuo string_path.split(".") .
  • type_of_function : 0 per l’accesso (non passare alcun valore al value ), 0 per aggiungere e modificare. 1 per cancellare.

Invece di una stringa, è ansible utilizzare un array che indirizzi oggetti e array annidati, ad esempio: ["my_field", "another_field", 0, "last_field", 10]

Ecco un esempio che cambierebbe un campo basato su questa rappresentazione di array. Sto usando qualcosa del genere in react.js per i campi di input controllati che cambiano lo stato delle strutture nidificate.

 let state = { test: "test_value", nested: { level1: "level1 value" }, arr: [1, 2, 3], nested_arr: { arr: ["buh", "bah", "foo"] } } function handleChange(value, fields) { let update_field = state; for(var i = 0; i < fields.length - 1; i++){ update_field = update_field[fields[i]]; } update_field[fields[fields.length-1]] = value; } handleChange("update", ["test"]); handleChange("update_nested", ["nested","level1"]); handleChange(100, ["arr",0]); handleChange('changed_foo', ["nested_arr", "arr", 3]); console.log(state); 

Sulla base di una risposta precedente, ho creato una funzione che può anche gestire parentesi. Ma nessun punto al loro interno a causa della divisione.

 function get(obj, str) { return str.split(/\.|\[/g).map(function(crumb) { return crumb.replace(/\]$/, '').trim().replace(/^(["'])((?:(?!\1)[^\\]|\\.)*?)\1$/, (match, quote, str) => str.replace(/\\(\\)?/g, "$1")); }).reduce(function(obj, prop) { return obj ? obj[prop] : undefined; }, obj); } 

Mentre la riduzione è buona, sono sorpreso che nessuno abbia mai usato per:

 function valueForKeyPath(obj, path){ const keys = path.split('.'); keys.forEach((key)=> obj = obj[key]); return obj; }; 

Test

 // (IE9+) Two steps var pathString = "[0]['property'].others[3].next['final']"; var obj = [{ property: { others: [1, 2, 3, { next: { final: "SUCCESS" } }] } }]; // Turn string to path array var pathArray = pathString .replace(/\[["']?([\w]+)["']?\]/g,".$1") .split(".") .splice(1); // Add object prototype method Object.prototype.path = function (path) { try { return [this].concat(path).reduce(function (f, l) { return f[l]; }); } catch (e) { console.error(e); } }; // usage console.log(obj.path(pathArray)); console.log(obj.path([0,"doesNotExist"])); 

Lavorare con la property o la propertyOf Underscore :

 var test = { foo: { bar: { baz: 'hello' } } } var string = 'foo.bar.baz'; // document.write(_.propertyOf(test)(string.split('.'))) document.write(_.property(string.split('.'))(test)); 
  

Che dire di questa soluzione:

 setJsonValue: function (json, field, val) { if (field !== undefined){ try { eval("json." + field + " = val"); } catch(e){ ; } } } 

E questo, per ottenere:

 getJsonValue: function (json, field){ var value = undefined; if (field !== undefined) { try { eval("value = json." + field); } catch(e){ ; } } return value; }; 

Probabilmente alcuni li considereranno insicuri, ma devono essere molto più veloci di allora, analizzando la stringa.

Costruire la risposta di Alnitak:

 if(!Object.prototype.byString){ //NEW byString which can update values Object.prototype.byString = function(s, v, o) { var _o = o || this; s = s.replace(/\[(\w+)\]/g, '.$1'); // CONVERT INDEXES TO PROPERTIES s = s.replace(/^\./, ''); // STRIP A LEADING DOT var a = s.split('.'); //ARRAY OF STRINGS SPLIT BY '.' for (var i = 0; i < a.length; ++i) {//LOOP OVER ARRAY OF STRINGS var k = a[i]; if (k in _o) {//LOOP THROUGH OBJECT KEYS if(_o.hasOwnProperty(k)){//USE ONLY KEYS WE CREATED if(v !== undefined){//IF WE HAVE A NEW VALUE PARAM if(i === a.length -1){//IF IT'S THE LAST IN THE ARRAY _o[k] = v; } } _o = _o[k];//NO NEW VALUE SO JUST RETURN THE CURRENT VALUE } } else { return; } } return _o; }; 

}

Questo ti permette di impostare anche un valore!

Ho creato un pacchetto npm e github anche con questo