Converti la stringa JavaScript in notazione punto in un riferimento ad oggetto


214

Dato un oggetto JS

var obj = { a: { b: '1', c: '2' } }

e una stringa

"a.b"

come posso convertire la stringa in notazione punto così posso andare

var val = obj.a.b

Se la stringa fosse giusta 'a', potrei usare obj[a]. Ma questo è più complesso. Immagino che ci sia un metodo semplice ma al momento sfugge.


25
@Andrey evalè cattivo; non usarlo
kevinji il

5
FYI: Ecco alcuni interessanti test di velocità che ho appena fatto: jsperf.com/dereference-object-property-path-from-string
James Wilkins,

se perf è una considerazione seria e stai riutilizzando molto gli stessi percorsi (ad es. all'interno di una funzione di filtro array), usa il costruttore Funzione come descritto nella mia risposta di seguito. Quando lo stesso percorso viene utilizzato migliaia di volte, il metodo Function può essere più di 10 volte più veloce di valutare o dividere e ridurre il percorso su ogni dereference.
Kevin Crumley,


Risposte:


437

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


4
reducenon è supportato in tutti i browser attualmente utilizzati.
Ricardo Tomasi,

14
@Ricardo: Array.reducefa parte dello standard ECMA-262. Se desideri davvero supportare browser obsoleti, puoi definire Array.prototype.reducel'implementazione di esempio fornita da qualche parte (ad es. Developer.mozilla.org/en/JavaScript/Reference/Global_Objects/… ).
ninjagecko,

2
Sì, ma è abbastanza facile mettere le due linee in una funzione. var setget = function( obj, path ){ function index( robj,i ) {return robj[i]}; return path.split('.').reduce( index, obj ); }
nevf,

3
Adoro questo esempio elegante, grazie ninjagecko. L'ho esteso per gestire la notazione in stile array, così come le stringhe vuote - vedi il mio esempio qui: jsfiddle.net/sc0ttyd/q7zyd
Sc0ttyD

2
@ninjagecko come lo trasformeresti anche in setter? Non solo restituendo i valori per percorso, ma impostandoli anche se un nuovo valore viene inviato nella funzione?
Swader,

64

Se puoi usare lodash , c'è una funzione, che fa esattamente questo:

_.get (oggetto, percorso, [defaultValue])

var val = _.get(obj, "a.b");

4
Nota: _.get(object, path)non si interrompe se non viene trovato un percorso. 'a.b.etc'.split('.').reduce((o,i)=>o[i], obj)lo fa. Per il mio caso specifico - non per tutti i casi - esattamente ciò di cui avevo bisogno. Grazie!
Sig. B.

1
@ Mr.B. l'ultima versione di Lodash ha un terzo argomento facoltativo per defaultValue. Il _.get()metodo restituisce il valore predefinito se viene _.get()risolto undefined, quindi impostalo su quello che desideri e osserva il valore impostato.
potenziato a vapore il

7
Per chiunque si chieda, supporta anche _.set(object, path, value).
Jeffrey Roosendaal,

19

Un esempio un po 'più coinvolto con la ricorsione.

function recompose(obj,string){
    var parts = string.split('.');
    var newObj = obj[parts[0]];
    if(parts[1]){
        parts.splice(0,1);
        var newString = parts.join('.');
        return recompose(newObj,newString);
    }
    return newObj;
}


var obj = { a: { b: '1', c: '2', d:{a:{b:'blah'}}}};

alert(recompose(obj,'a.d.a.b')); //blah

19

puoi anche usare lodash.get

Basta installare questo pacchetto (npm i --save lodash.get) e quindi utilizzarlo in questo modo:

const get = require('lodash.get');

const myObj = { user: { firstName: 'Stacky', lastName: 'Overflowy' }, id: 123 };

console.log(get(myObj, 'user.firstName')); // prints Stacky
console.log(get(myObj, 'id')); //prints  123

//You can also update values
get(myObj, 'user').firstName = John;

10

Se ti aspetti di ripetere lo stesso percorso molte volte, la creazione di una funzione per ciascun percorso di notazione dei punti ha di gran lunga le prestazioni migliori (espandendo i test di perf a cui James Wilkins è collegato nei commenti sopra).

var path = 'a.b.x';
var getter = new Function("obj", "return obj." + path + ";");
getter(obj);

L'uso del costruttore Function ha alcuni degli stessi inconvenienti di eval () in termini di sicurezza e prestazioni nel caso peggiore, ma IMO è uno strumento mal utilizzato per i casi in cui è necessaria una combinazione di dinamismo estremo e prestazioni elevate. Uso questa metodologia per creare funzioni di filtro di array e chiamarle all'interno di un ciclo digest AngularJS. I miei profili mostrano costantemente il passo array.filter () che impiega meno di 1ms per dereferenziare e filtrare circa 2000 oggetti complessi, usando percorsi definiti dinamicamente 3-4 livelli di profondità.

Una metodologia simile potrebbe essere utilizzata per creare funzioni setter, ovviamente:

var setter = new Function("obj", "newval", "obj." + path + " = newval;");
setter(obj, "some new val");

1
se devi distanziare gli stessi percorsi a lungo, il link jsperf.com sopra mostra un esempio di come salvare e cercare la funzione in un secondo momento. L'atto di chiamare il costruttore della funzione è piuttosto lento, quindi il codice high-perf dovrebbe memorizzare i risultati per evitare di ripeterlo, se possibile.
Kevin Crumley,


7

Suggerisco di dividere il percorso, iterarlo e ridurre l'oggetto che hai. Questa proposta funziona con un valore predefinito per le proprietà mancanti.

const getValue = (object, keys) => keys.split('.').reduce((o, k) => (o || {})[k], object);

console.log(getValue({ a: { b: '1', c: '2' } }, 'a.b'));
console.log(getValue({ a: { b: '1', c: '2' } }, 'foo.bar.baz'));


6

Nota se stai già utilizzando Lodash puoi usare le funzioni propertyo get:

var obj = { a: { b: '1', c: '2' } };
_.property('a.b')(obj); // => 1
_.get(obj, 'a.b'); // => 1

Underscore ha anche una propertyfunzione ma non supporta la notazione punto.


1
Sembra che Underscore non supporti la notazione punto. Risposta aggiornata
Tamlyn,

5

Altre proposte sono un po 'enigmatiche, quindi ho pensato di contribuire:

Object.prop = function(obj, prop, val){
    var props = prop.split('.')
      , final = props.pop(), p 
    while(p = props.shift()){
        if (typeof obj[p] === 'undefined')
            return undefined;
        obj = obj[p]
    }
    return val ? (obj[final] = val) : obj[final]
}

var obj = { a: { b: '1', c: '2' } }

// get
console.log(Object.prop(obj, 'a.c')) // -> 2
// set
Object.prop(obj, 'a.c', function(){})
console.log(obj) // -> { a: { b: '1', c: [Function] } }

5

È possibile utilizzare la libreria disponibile su npm, il che semplifica questo processo. https://www.npmjs.com/package/dot-object

 var dot = require('dot-object');

var obj = {
 some: {
   nested: {
     value: 'Hi there!'
   }
 }
};

var val = dot.pick('some.nested.value', obj);
console.log(val);

// Result: Hi there!

1
Grazie per averlo portato alla nostra attenzione. Sembra molto utile
nevf,

Ed è per questo che ogni progetto inietta 10000 dipendenze :-).
Marvin,

4
var a = { b: { c: 9 } };

function value(layer, path, value) {
    var i = 0,
        path = path.split('.');

    for (; i < path.length; i++)
        if (value != null && i + 1 === path.length)
            layer[path[i]] = value;
        layer = layer[path[i]];

    return layer;
};

value(a, 'b.c'); // 9

value(a, 'b.c', 4);

value(a, 'b.c'); // 4

Questo è un sacco di codice rispetto al evalmodo molto più semplice di farlo, ma come dice Simon Willison, non dovresti mai usare eval .

Inoltre, JSFiddle .


Ottimo, funziona alla grande ed è più corto di CD Sanchez. Grazie.
nevf,

value (a, 'bbbbb') non restituisce undefined
frumbert

4

Ho esteso l'elegante risposta di ninjagecko in modo che la funzione gestisca riferimenti sia in stile punteggiato che / o array e che una stringa vuota causi la restituzione dell'oggetto padre.

Ecco qui:

string_to_ref = function (object, reference) {
    function arr_deref(o, ref, i) { return !ref ? o : (o[ref.slice(0, i ? -1 : ref.length)]) }
    function dot_deref(o, ref) { return ref.split('[').reduce(arr_deref, o); }
    return !reference ? object : reference.split('.').reduce(dot_deref, object);
};

Guarda il mio esempio di lavoro jsFiddle qui: http://jsfiddle.net/sc0ttyd/q7zyd/


Davvero un'ottima soluzione. C'è solo un problema, si presume che la []notazione sia sempre per gli array. ci possono essere anche le chiavi dell'oggetto rappresentate in questo modo, ad esempio obj['some-problem/name'].list[1]Per risolvere questo problema ho dovuto aggiornare la arr_dereffunzione in questo modo javascript function arr_deref(o, ref, i) { return !ref ? o : (o[(ref.slice(0, i ? -1 : ref.length)).replace(/^['"]|['"]$/g, '')]); }
Serkan Yersen,

1
Anche se, al giorno d'oggi, non lo farei. Userei Lodash: lodash.com/docs#get
Sc0ttyD il

2
var find = function(root, path) {
  var segments = path.split('.'),
      cursor = root,
      target;

  for (var i = 0; i < segments.length; ++i) {
   target = cursor[segments[i]];
   if (typeof target == "undefined") return void 0;
   cursor = target;
  }

  return cursor;
};

var obj = { a: { b: '1', c: '2' } }
find(obj, "a.b"); // 1

var set = function (root, path, value) {
   var segments = path.split('.'),
       cursor = root,
       target;

   for (var i = 0; i < segments.length - 1; ++i) {
      cursor = cursor[segments[i]] || { };
   }

   cursor[segments[segments.length - 1]] = value;
};

set(obj, "a.k", function () { console.log("hello world"); });

find(obj, "a.k")(); // hello world

Grazie per tutte le risposte rapide. Non mi piacciono le soluzioni eval (). Questo e post simili sembrano i migliori. Tuttavia sto ancora riscontrando un problema. Sto cercando di impostare il valore obj.ab = nuovo valore. Per essere precisi, il valore di b è una funzione, quindi ho bisogno di usare obj.ab (new_value). La funzione viene chiamata ma il valore non è impostato. Penso che sia un problema di portata ma sto ancora scavando. Mi rendo conto che questo non rientra nell'ambito della domanda originale. Il mio codice utilizza Knockout.js eb è un ko.observable.
nevf,

@nevf: ho aggiunto una seconda funzione che penso faccia quello che vuoi. È possibile personalizzarlo a proprio piacimento in base al comportamento desiderato (ad es. Dovrebbe creare gli oggetti se non esistono ?, ecc.).
Cristian Sanchez,

@nevf Ma il mio lo fa con una sola funzione. ; D
McKayla,

grazie per l'aggiornamento che sono stato in grado di utilizzare. @tylermwashburn - e grazie per la tua implementazione più breve che funziona anche a meraviglia. Buon w / e tutto.
nevf,

2

È possibile ottenere il valore di un membro oggetto tramite notazione punto con una singola riga di codice:

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

Nel tuo caso:

var obj = { a: { b: '1', c: '2' } }
var val = new Function('_', 'return _.a.b')(obj);

Per semplificare, puoi scrivere una funzione come questa:

function objGet(obj, path){
    return new Function('_', 'return _.' + path)(obj);
}

Spiegazione:

Il costruttore della funzione crea un nuovo oggetto funzione. In JavaScript ogni funzione è in realtà un oggetto Function. La sintassi per creare esplicitamente una funzione con il costruttore Funzione è:

new Function ([arg1[, arg2[, ...argN]],] functionBody)

dove arguments(arg1 to argN)deve essere una stringa che corrisponde a un identificatore javaScript valido efunctionBody è una stringa contenente le istruzioni javaScript che comprendono la definizione della funzione.

Nel nostro caso sfruttiamo il corpo della funzione stringa per recuperare un membro oggetto con notazione punto.

Spero che sia d'aiuto.


Puoi spiegare esattamente cosa sta facendo questo? E come passeresti 'ab' ecc. Come parametro di funzione?
nevf,

1
Ho dato un +1 ma JSLint avverte che "il costruttore della funzione è una forma di valutazione" .
Gabe,

2

OTTIENI / RISPONDI risposta che funziona anche con reattivo nativo (al Object.prototypemomento non puoi assegnarlo ):

Object.defineProperty(Object.prototype, 'getNestedProp', {
    value: function(desc) {
        var obj = this;
        var arr = desc.split(".");
        while(arr.length && (obj = obj[arr.shift()]));
        return obj;
    },
    enumerable: false
});

Object.defineProperty(Object.prototype, 'setNestedProp', {
    value: function(desc, value) {
        var obj = this;
        var arr = desc.split(".");
        var last = arr.pop();
        while(arr.length && (obj = obj[arr.shift()]));
        obj[last] = value;
    },
    enumerable: false
});

Uso:

var a = { values: [{ value: null }] };
var b = { one: { two: 'foo' } };

a.setNestedProp('values.0.value', b.getNestedProp('one.two'));
console.log(a.values[0].value); // foo

1

Ho copiato quanto segue dalla risposta di Ricardo Tomasi e modificato per creare anche oggetti secondari che non esistono ancora se necessario. È un po 'meno efficiente (di piùif creazione di oggetti vuoti), ma dovrebbe essere abbastanza buono.

Inoltre, ci permetterà di fare Object.prop(obj, 'a.b', false)dove prima non potevamo. Sfortunatamente, non ci permetterà di assegnare undefined... Non so ancora come procedere.

/**
 * Object.prop()
 *
 * Allows dot-notation access to object properties for both getting and setting.
 *
 * @param {Object} obj    The object we're getting from or setting
 * @param {string} prop   The dot-notated string defining the property location
 * @param {mixed}  val    For setting only; the value to set
 */
 Object.prop = function(obj, prop, val){
   var props = prop.split('.'),
       final = props.pop(),
       p;

   for (var i = 0; i < props.length; i++) {
     p = props[i];
     if (typeof obj[p] === 'undefined') {
       // If we're setting
       if (typeof val !== 'undefined') {
         // If we're not at the end of the props, keep adding new empty objects
         if (i != props.length)
           obj[p] = {};
       }
       else
         return undefined;
     }
     obj = obj[p]
   }
   return typeof val !== "undefined" ? (obj[final] = val) : obj[final]
 }

1

Se si desidera convertire qualsiasi oggetto che contenga chiavi di notazione punto in una versione in serie di tali chiavi, è possibile utilizzare questo.


Questo convertirà qualcosa di simile

{
  name: 'Andy',
  brothers.0: 'Bob'
  brothers.1: 'Steve'
  brothers.2: 'Jack'
  sisters.0: 'Sally'
}

per

{
  name: 'Andy',
  brothers: ['Bob', 'Steve', 'Jack']
  sisters: ['Sally']
}

convertDotNotationToArray(objectWithDotNotation) {

    Object.entries(objectWithDotNotation).forEach(([key, val]) => {

      // Is the key of dot notation 
      if (key.includes('.')) {
        const [name, index] = key.split('.');

        // If you have not created an array version, create one 
        if (!objectWithDotNotation[name]) {
          objectWithDotNotation[name] = new Array();
        }

        // Save the value in the newly created array at the specific index 
        objectWithDotNotation[name][index] = val;
        // Delete the current dot notation key val
        delete objectWithDotNotation[key];
      }
    });

}

Non ha elaborato i valori con "brothers.0.one" come JSON.
Karthick,

Hai qualche suggerimento per l'elaborazione di JSON più complessi con la raccolta di array.?
Karthick,

0

Ecco il mio codice senza usare eval. È anche facile da capire.

function value(obj, props) {
  if (!props) return obj;
  var propsArr = props.split('.');
  var prop = propsArr.splice(0, 1);
  return value(obj[prop], propsArr.join('.'));
}

var obj = { a: { b: '1', c: '2', d:{a:{b:'blah'}}}};

console.log(value(obj, 'a.d.a.b')); //returns blah

0

Sì, è stato chiesto 4 anni fa e sì, l'estensione dei prototipi di base non è di solito una buona idea ma, se si mantengono tutte le estensioni in un unico posto, potrebbero essere utili.
Quindi, ecco il mio modo di farlo.

   Object.defineProperty(Object.prototype, "getNestedProperty", {
    value     : function (propertyName) {
        var result = this;
        var arr = propertyName.split(".");

        while (arr.length && result) {
            result = result[arr.shift()];
        }

        return result;
    },
    enumerable: false
});

Ora sarai in grado di ottenere la proprietà annidata ovunque senza importare il modulo con la funzione o la funzione copia / incolla.

UPD.Example:

{a:{b:11}}.getNestedProperty('a.b'); //returns 11

UPD 2. La prossima estensione rompe la mangusta nel mio progetto. Inoltre ho letto che potrebbe aver rotto jquery. Quindi, non farlo mai nel prossimo modo

 Object.prototype.getNestedProperty = function (propertyName) {
    var result = this;
    var arr = propertyName.split(".");

    while (arr.length && result) {
        result = result[arr.shift()];
    }

    return result;
};

0

Ecco la mia implementazione

Implementazione 1

Object.prototype.access = function() {
    var ele = this[arguments[0]];
    if(arguments.length === 1) return ele;
    return ele.access.apply(ele, [].slice.call(arguments, 1));
}

Implementazione 2 (utilizzando array ridurre invece di slice)

Object.prototype.access = function() {
    var self = this;
    return [].reduce.call(arguments,function(prev,cur) {
        return prev[cur];
    }, self);
}

Esempi:

var myobj = {'a':{'b':{'c':{'d':'abcd','e':[11,22,33]}}}};

myobj.access('a','b','c'); // returns: {'d':'abcd', e:[0,1,2,3]}
myobj.a.b.access('c','d'); // returns: 'abcd'
myobj.access('a','b','c','e',0); // returns: 11

può anche gestire oggetti all'interno di array come per

var myobj2 = {'a': {'b':[{'c':'ab0c'},{'d':'ab1d'}]}}
myobj2.access('a','b','1','d'); // returns: 'ab1d'

0

Questa è la mia soluzione estesa proposta da: ninjagecko

Per me la semplice notazione con stringhe non era abbastanza, quindi la versione seguente supporta cose come:

index (obj, 'data.accounts [0] .address [0] .postcode');

/**
 * Get object by index
 * @supported
 * - arrays supported
 * - array indexes supported
 * @not-supported
 * - multiple arrays
 * @issues:
 *  index(myAccount, 'accounts[0].address[0].id') - works fine
 *  index(myAccount, 'accounts[].address[0].id') - doesnt work
 * @Example:
 * index(obj, 'data.accounts[].id') => returns array of id's
 * index(obj, 'data.accounts[0].id') => returns id of 0 element from array
 * index(obj, 'data.accounts[0].addresses.list[0].id') => error
 * @param obj
 * @param path
 * @returns {any}
 */
var index = function(obj, path, isArray?, arrIndex?){

    // is an array
    if(typeof isArray === 'undefined') isArray = false;
    // array index,
    // if null, will take all indexes
    if(typeof arrIndex === 'undefined') arrIndex = null;

    var _arrIndex = null;

    var reduceArrayTag = function(i, subArrIndex){
        return i.replace(/(\[)([\d]{0,})(\])/, (i) => {
            var tmp = i.match(/(\[)([\d]{0,})(\])/);
            isArray = true;
            if(subArrIndex){
                _arrIndex =  (tmp[2] !== '') ? tmp[2] : null;
            }else{
                arrIndex =  (tmp[2] !== '') ? tmp[2] : null;
            }
            return '';
        });
    }

    function byIndex(obj, i) {
        // if is an array
        if(isArray){
            isArray = false;
            i = reduceArrayTag(i, true);
            // if array index is null,
            // return an array of with values from every index
            if(!arrIndex){
                var arrValues = [];
                _.forEach(obj, (el) => {
                    arrValues.push(index(el, i, isArray, arrIndex));
                })
                return arrValues;
            }
            // if array index is specified
            var value = obj[arrIndex][i];
            if(isArray){
                arrIndex = _arrIndex;
            }else{
                arrIndex = null;
            }
            return value;
        }else{
            // remove [] from notation,
            // if [] has been removed, check the index of array
            i = reduceArrayTag(i, false);
            return obj[i]
        }
    }

    // reduce with byIndex method
    return path.split('.').reduce(byIndex, obj)
}

0

A rischio di battere un cavallo morto ... Lo trovo molto utile nel attraversare oggetti nidificati per fare riferimento a dove ti trovi rispetto all'oggetto base o ad un oggetto simile con la stessa struttura. A tal fine, ciò è utile con una funzione di attraversamento di oggetti nidificati. Nota che ho usato un array per contenere il percorso. Sarebbe banale modificarlo per utilizzare un percorso stringa o un array. Si noti inoltre che è possibile assegnare "undefined" al valore, diversamente da altre implementazioni.

/*
 * Traverse each key in a nested object and call fn(curObject, key, value, baseObject, path)
 * on each. The path is an array of the keys required to get to curObject from
 * baseObject using objectPath(). If the call to fn() returns falsey, objects below
 * curObject are not traversed. Should be called as objectTaverse(baseObject, fn).
 * The third and fourth arguments are only used by recursion.
 */
function objectTraverse (o, fn, base, path) {
    path = path || [];
    base = base || o;
    Object.keys(o).forEach(function (key) {
        if (fn(o, key, o[key], base, path) && jQuery.isPlainObject(o[key])) {
            path.push(key);
            objectTraverse(o[key], fn, base, path);
            path.pop();
        }
    });
}

/*
 * Get/set a nested key in an object. Path is an array of the keys to reference each level
 * of nesting. If value is provided, the nested key is set.
 * The value of the nested key is returned.
 */
function objectPath (o, path, value) {
    var last = path.pop();

    while (path.length && o) {
        o = o[path.shift()];
    }
    if (arguments.length < 3) {
        return (o? o[last] : o);
    }
    return (o[last] = value);
}


0

Ho usato questo codice nel mio progetto

const getValue = (obj, arrPath) => (
  arrPath.reduce((x, y) => {
    if (y in x) return x[y]
    return {}
  }, obj)
)

Uso:

const obj = { id: { user: { local: 104 } } }
const path = [ 'id', 'user', 'local' ]
getValue(obj, path) // return 104

0

Pochi anni dopo, ho scoperto che gestisce portata e array. per esempioa['b']["c"].d.etc

function getScopedObj(scope, str) {
  let obj=scope, arr;

  try {
    arr = str.split(/[\[\]\.]/) // split by [,],.
      .filter(el => el)             // filter out empty one
      .map(el => el.replace(/^['"]+|['"]+$/g, '')); // remove string quotation
    arr.forEach(el => obj = obj[el])
  } catch(e) {
    obj = undefined;
  }

  return obj;
}

window.a = {b: {c: {d: {etc: 'success'}}}}

getScopedObj(window, `a.b.c.d.etc`)             // success
getScopedObj(window, `a['b']["c"].d.etc`)       // success
getScopedObj(window, `a['INVALID']["c"].d.etc`) // undefined

-1

Non è chiaro quale sia la tua domanda. Dato il tuo oggetto, obj.a.bti darebbe "2" così com'è. Se si desidera manipolare la stringa per utilizzare le parentesi, è possibile effettuare questa operazione:

var s = 'a.b';
s = 'obj["' + s.replace(/\./g, '"]["') + '"]';
alert(s); // displays obj["a"]["b"]

1
Questo non funziona a.b.c, e non realizza davvero quello che vogliono .. Vogliono il valore, non un evalpercorso.
McKayla,

Ora l'ho risolto in modo che funzioni a.b.c, ma hai ragione, a quanto pare voleva ottenere / impostare il valore della proprietà su obj.a.b. La domanda mi ha confuso, dal momento che ha detto che voleva "convertire la stringa" ....
Mark Eirich

Buon lavoro. :) Era un po 'vago. Tuttavia, hai fatto un buon lavoro di conversione.
McKayla,

-1

ecco i miei 10 centesimi, la seguente funzione verrà / impostata in base al percorso fornito, .. certo che puoi migliorarlo, rimuovere || e sostituiscilo con Object.hasOwnPropertyse ti occupi erroneamente di valori falsi,

l'ho provato con a.b.ce ab2.c{a:{b:[0,1,{c:7}]}} e funziona sia per l'impostazione sia per ottenere :).

Cheerz

function helper(obj, path, setValue){
  const l = String(path).split('.');
  return l.reduce((o,i, idx)=>{
   if( l.length-idx===1)  { o[i] = setValue || o[i];return setValue ? obj : o[i];}
  o[i] = o[i] || {};
   return o[i];
  }, x)
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.