nota recente: se sono lusingato che questa risposta abbia ottenuto molti voti positivi, sono anche un po 'inorridita. Se uno ha bisogno di convertire stringhe di notazione punti come "xabc" in riferimenti, potrebbe (forse) essere un segno che sta succedendo qualcosa di molto sbagliato (a meno che tu non stia eseguendo una strana deserializzazione).
Vale a dire, i novizi che trovano la loro strada per questa risposta devono porsi la domanda "perché lo sto facendo?"
Ovviamente è generalmente bene farlo se il tuo caso d'uso è piccolo e non incorrerai in problemi di prestazioni, E non dovrai basarti sull'astrazione per renderlo più complicato in seguito. In effetti, se questo ridurrà la complessità del codice e renderà le cose semplici, probabilmente dovresti andare avanti e fare ciò che OP richiede. Tuttavia, in caso contrario, considera se si applica una di queste condizioni:
caso 1 : come metodo principale per lavorare con i tuoi dati (ad es. come forma predefinita della tua app di passare oggetti e dereferenziarli). Come chiedere "come posso cercare una funzione o il nome di una variabile da una stringa".
- Questa è una cattiva pratica di programmazione (metaprogrammazione non necessaria in particolare, e il tipo di violazioni ha uno stile di codifica privo di effetti collaterali e avrà successi nelle prestazioni). I principianti che si trovano in questo caso, dovrebbero invece considerare di lavorare con rappresentazioni di array, ad esempio ['x', 'a', 'b', 'c'], o anche qualcosa di più diretto / semplice / chiaro se possibile: come non perdere traccia dei riferimenti stessi in primo luogo (ideale se è solo lato client o solo lato server), ecc. (Un ID univoco preesistente sarebbe inelegante da aggiungere, ma potrebbe essere utilizzato se la specifica richiede diversamente il suo esistenza a prescindere.)
caso 2 : utilizzo di dati serializzati o dati che verranno visualizzati all'utente. Come usare una data come stringa "1999-12-30" piuttosto che un oggetto Date (che può causare errori di fuso orario o complessità di serializzazione aggiunta se non attenta). Oppure sai cosa stai facendo.
- Questo forse va bene. Fai attenzione che non ci siano stringhe di punti "." nei frammenti di input disinfettati.
Se ti ritrovi a utilizzare questa risposta continuamente e a convertire avanti e indietro tra stringa e matrice, potresti essere nel brutto caso e dovresti considerare un'alternativa.
Ecco un elegante one-liner che è 10 volte più corto rispetto alle altre soluzioni:
function index(obj,i) {return obj[i]}
'a.b.etc'.split('.').reduce(index, obj)
[modifica] O in ECMAScript 6:
'a.b.etc'.split('.').reduce((o,i)=>o[i], obj)
(Non che penso che eval sia sempre cattivo come altri suggeriscono che lo sia (anche se di solito lo è), tuttavia quelle persone saranno contente che questo metodo non usi eval. Quanto sopra troverà obj.a.b.etcdato obje la stringa "a.b.etc".)
In risposta a coloro che hanno ancora paura di usare reducenonostante sia conforme allo standard ECMA-262 (5a edizione), ecco un'implementazione ricorsiva su due righe:
function multiIndex(obj,is) { // obj,['1','2','3'] -> ((obj['1'])['2'])['3']
return is.length ? multiIndex(obj[is[0]],is.slice(1)) : obj
}
function pathIndex(obj,is) { // obj,'1.2.3' -> multiIndex(obj,['1','2','3'])
return multiIndex(obj,is.split('.'))
}
pathIndex('a.b.etc')
A seconda delle ottimizzazioni che sta eseguendo il compilatore JS, è possibile assicurarsi che eventuali funzioni nidificate non vengano ridefinite ad ogni chiamata tramite i metodi usuali (collocandole in uno spazio di chiusura, oggetto o spazio dei nomi globale).
modifica :
Per rispondere a una domanda interessante nei commenti:
come trasformeresti anche questo in un setter? Non solo restituendo i valori per percorso, ma impostandoli anche se un nuovo valore viene inviato nella funzione? - Swader, 28 giugno alle 21:42
(sidenote: purtroppo non è possibile restituire un oggetto con un Setter, in quanto ciò violerebbe la convenzione di chiamata; il commentatore sembra invece riferirsi a una funzione generale in stile setter con effetti collaterali come index(obj,"a.b.etc", value)fare obj.a.b.etc = value.)
Lo reducestile non è davvero adatto a quello, ma possiamo modificare l'implementazione ricorsiva:
function index(obj,is, value) {
if (typeof is == 'string')
return index(obj,is.split('.'), value);
else if (is.length==1 && value!==undefined)
return obj[is[0]] = value;
else if (is.length==0)
return obj;
else
return index(obj[is[0]],is.slice(1), value);
}
demo:
> obj = {a:{b:{etc:5}}}
> index(obj,'a.b.etc')
5
> index(obj,['a','b','etc']) #works with both strings and lists
5
> index(obj,'a.b.etc', 123) #setter-mode - third argument (possibly poor form)
123
> index(obj,'a.b.etc')
123
... sebbene personalmente consiglierei di fare una funzione separata setIndex(...). Vorrei concludere con una nota a margine che l'originale della domanda potrebbe (dovrebbe?) Lavorare con matrici di indici (da cui possono ricavare .split), piuttosto che stringhe; sebbene di solito non ci sia nulla di sbagliato in una funzione di convenienza.
Un commentatore ha chiesto:
che dire di array? qualcosa come "ab [4] .cd [1] [2] [3]"? -AlexS
Javascript è un linguaggio molto strano; in generale gli oggetti possono avere solo stringhe come chiavi di proprietà, quindi per esempio se xfosse un oggetto generico come x={}, allora x[1]diventerebbe x["1"]... hai letto bene ... yup ...
Le matrici Javascript (che sono esse stesse istanze di Object) incoraggiano specificamente le chiavi intere, anche se potresti fare qualcosa del genere x=[]; x["puppy"]=5;.
Ma in generale (e ci sono eccezioni), x["somestring"]===x.somestring (quando è permesso; non puoi farlo x.123).
(Tieni presente che qualsiasi compilatore JS che stai utilizzando potrebbe scegliere, forse, di compilare questi in rappresentazioni più sanitarie se può provare che non violerebbe le specifiche.)
Quindi la risposta alla tua domanda dipenderà dal fatto che stai assumendo che quegli oggetti accettino solo numeri interi (a causa di una restrizione nel tuo dominio problematico) o meno. Supponiamo di no. Quindi un'espressione valida è una concatenazione di un identificatore di base più alcuni .identifiers più alcuni ["stringindex"]s
Questo sarebbe quindi equivalente a a["b"][4]["c"]["d"][1][2][3], anche se probabilmente dovremmo anche sostenere a.b["c\"validjsstringliteral"][3]. Dovresti controllare la sezione grammaticale ecmascript sui letterali di stringa per vedere come analizzare un valore letterale di stringa valido. Tecnicamente dovresti anche verificare (diversamente dalla mia prima risposta) che asia un identificatore javascript valido .
Una semplice risposta alla tua domanda, tuttavia, se le tue stringhe non contengono virgole o parentesi , sarebbe solo quella di abbinare sequenze di lunghezza 1+ di caratteri non nel set ,o [oppure ]:
> "abc[4].c.def[1][2][\"gh\"]".match(/[^\]\[.]+/g)
// ^^^ ^ ^ ^^^ ^ ^ ^^^^^
["abc", "4", "c", "def", "1", "2", ""gh""]
Se le tue stringhe non contengono caratteri di escape o "caratteri e poiché IdentifierNames è una sotto lingua di StringLiterals (penso ???) puoi prima convertire i tuoi punti in []:
> var R=[], demoString="abc[4].c.def[1][2][\"gh\"]";
> for(var match,matcher=/^([^\.\[]+)|\.([^\.\[]+)|\["([^"]+)"\]|\[(\d+)\]/g;
match=matcher.exec(demoString); ) {
R.push(Array.from(match).slice(1).filter(x=>x!==undefined)[0]);
// extremely bad code because js regexes are weird, don't use this
}
> R
["abc", "4", "c", "def", "1", "2", "gh"]
Naturalmente, fai sempre attenzione e non fidarti mai dei tuoi dati. Alcuni cattivi modi per farlo che potrebbero funzionare in alcuni casi d'uso includono anche:
// hackish/wrongish; preprocess your string into "a.b.4.c.d.1.2.3", e.g.:
> yourstring.replace(/]/g,"").replace(/\[/g,".").split(".")
"a.b.4.c.d.1.2.3" //use code from before
Modifica speciale 2018:
Andiamo al punto di partenza e facciamo la soluzione più inefficiente, orribilmente sovrastimata da un piano che possiamo trovare ... nell'interesse del settore della purezza sintattica . Con gli oggetti Proxy ES6! ... Definiamo anche alcune proprietà che (sono belle e meravigliose ma) possono rompere le librerie scritte in modo errato. Forse dovresti essere cauto nell'usarlo se ti preoccupi di prestazioni, sanità mentale (il tuo o degli altri), il tuo lavoro, ecc.
// [1,2,3][-1]==3 (or just use .slice(-1)[0])
if (![1][-1])
Object.defineProperty(Array.prototype, -1, {get() {return this[this.length-1]}}); //credit to caub
// WARNING: THIS XTREME™ RADICAL METHOD IS VERY INEFFICIENT,
// ESPECIALLY IF INDEXING INTO MULTIPLE OBJECTS,
// because you are constantly creating wrapper objects on-the-fly and,
// even worse, going through Proxy i.e. runtime ~reflection, which prevents
// compiler optimization
// Proxy handler to override obj[*]/obj.* and obj[*]=...
var hyperIndexProxyHandler = {
get: function(obj,key, proxy) {
return key.split('.').reduce((o,i)=>o[i], obj);
},
set: function(obj,key,value, proxy) {
var keys = key.split('.');
var beforeLast = keys.slice(0,-1).reduce((o,i)=>o[i], obj);
beforeLast[keys[-1]] = value;
},
has: function(obj,key) {
//etc
}
};
function hyperIndexOf(target) {
return new Proxy(target, hyperIndexProxyHandler);
}
demo:
var obj = {a:{b:{c:1, d:2}}};
console.log("obj is:", JSON.stringify(obj));
var objHyper = hyperIndexOf(obj);
console.log("(proxy override get) objHyper['a.b.c'] is:", objHyper['a.b.c']);
objHyper['a.b.c'] = 3;
console.log("(proxy override set) objHyper['a.b.c']=3, now obj is:", JSON.stringify(obj));
console.log("(behind the scenes) objHyper is:", objHyper);
if (!({}).H)
Object.defineProperties(Object.prototype, {
H: {
get: function() {
return hyperIndexOf(this); // TODO:cache as a non-enumerable property for efficiency?
}
}
});
console.log("(shortcut) obj.H['a.b.c']=4");
obj.H['a.b.c'] = 4;
console.log("(shortcut) obj.H['a.b.c'] is obj['a']['b']['c'] is", obj.H['a.b.c']);
Produzione:
obj è: {"a": {"b": {"c": 1, "d": 2}}}
(proxy override get) objHyper ['abc'] è: 1
(proxy override set) objHyper ['abc'] = 3, ora obj è: {"a": {"b": {"c": 3, "d": 2}}}
(dietro le quinte) objHyper è: Proxy {a: {…}}
(collegamento) obj.H ['abc'] = 4
(scorciatoia) obj.H ['abc'] è obj ['a'] ['b'] ['c'] è: 4
idea inefficiente: è possibile modificare quanto sopra da inviare in base all'argomento di input; utilizzare il .match(/[^\]\[.]+/g)metodo per supportare obj['keys'].like[3]['this'], oppure if instanceof Array, quindi accettare una matrice come input come keys = ['a','b','c']; obj.H[keys].
Per suggerimento che potresti voler gestire indici indefiniti in un modo NaN "più morbido" (ad es. index({a:{b:{c:...}}}, 'a.x.c')Restituire un TypeError indefinito piuttosto che non rilevato) ...:
1) Ciò ha senso dal punto di vista "dovremmo restituire undefined anziché gettare un errore" nella situazione dell'indice monodimensionale ({}) ['eg']] == undefined, quindi "dovremmo restituire undefined piuttosto che lanciare un errore "nella situazione N-dimensionale.
2) Questo non ha senso dal punto di vista che stiamo facendo x['a']['x']['c'], che fallirebbe con un TypeError nell'esempio sopra.
Detto questo, faresti funzionare questo sostituendo la tua funzione di riduzione con:
(o,i)=>o===undefined?undefined:o[i], o
(o,i)=>(o||{})[i] .
(È possibile renderlo più efficiente utilizzando un ciclo for e interrompendo / restituendo ogni volta che il sottosuolo su cui si accederà all'indice successivo non è definito, oppure utilizzando un tentativo di cattura se si prevede che tali guasti siano sufficientemente rari.)
evalè cattivo; non usarlo