Accesso a oggetti JavaScript e nidificati nidificati tramite percorso stringa


454

Ho una struttura di 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 someObject.part1.nameil valore di, che è "Part 1". Stessa cosa con part2quantity che ha riempito con 60.

Esiste un modo per raggiungere questo obiettivo con javascript puro o JQuery?


Non sei sicuro di quello che stai chiedendo qui? Vuoi essere in grado di interrogare part1.name e avere il testo "part1.name" restituito? O vuoi un mezzo per ottenere il valore memorizzato in part1.name?
BonyT,

hai provato a fare come var part1name = someObject.part1name;`
Rafay il

1
@BonyT: Voglio interrogare someObject.part1.name e restituirne il valore ("Parte 1"). Tuttavia, desidero che la query (l'ho chiamata "chiave") sia memorizzata in una variabile "part1name". Grazie per la tua risposta. @ 3nigma: certamente. Ma questa non è la mia intenzione. Grazie per la risposta.
Komaruloh,

1
nella risposta duplicato, amo di FYR risposta stackoverflow.com/questions/8817394/...
Steve Nero

Risposte:


520

Ho appena fatto questo sulla base di 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 viene passata una stringa in cui gli indici più a sinistra non corrispondono a una voce nidificata correttamente all'interno dell'oggetto. Questa è una preoccupazione valida, ma IMHO è meglio affrontarla con un try / catchblocco quando chiama, piuttosto che avere questa funzione in silenzio undefinedper un indice non valido.


19
Funziona magnificamente. Per favore, contribuisci a Internet avvolgendolo come pacchetto di nodi.
t3dodson,

14
@ t3dodson Ho appena fatto: github.com/capaj/object-resolve-path basta essere consapevoli del fatto che questo non funziona bene quando il nome della tua proprietà contiene '[]' in sé. Regex lo sostituirà con "." e non funziona come previsto
Capaj

20
grandi cose; usando la biblioteca lodash, si può anche fare:_.get(object, nestedPropertyString);
ian

17
Questo probabilmente si perderà nel mare dei commenti, tuttavia si verifica un errore se si tenta di indirizzare una proprietà che non esiste. Così 'part3[0].name.iDontExist'. L'aggiunta di un segno di spunta per vedere se oè un oggetto nelle if incorrezioni del problema. (Il modo in cui lo fai dipende da te). Vedi violino aggiornato: jsfiddle.net/hEsys/418
ste2425

2
Questo è così oro. Abbiamo un'applicazione basata sulla configurazione e questo è abbastanza utile! Grazie!
Christian Esperar,

182

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 è possibile utilizzare parentesi quadre ( []) per gli indici di matrice, sebbene la specifica di indici di matrice tra il token di separazione (ad es. .) Funzioni correttamente come mostrato sopra.

7
usare ridurre è un'ottima soluzione (si può usare anche _.reduce()dalla sottolineatura o dalla libreria lodash)
Alp

3
Penso che selfprobabilmente qui non sia definito. Vuoi dire this?
Platinum Azure,

2
Ecco il mio complemento per impostare i valori per percorso: pastebin.com/jDp5sKT9
mroach

1
Qualcuno sa come portarlo su TypeScript?
Adam Plocher,

1
@ SC1000 buona idea. Questa risposta è stata scritta prima che i parametri predefiniti fossero disponibili nella maggior parte dei browser. Lo aggiornerò su "function resolve (path, obj = self)", poiché il riferimento all'oggetto globale come predefinito è intenzionale.
speigg

179

Questo è ora supportato da lodash utilizzando _.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, 'a.b.c', 'default');
// → 'default'

9
Questa dovrebbe essere l'unica risposta accettata, perché questa è l'unica che funziona sia per la sintassi punto che per parentesi e non fallisce, quando abbiamo '[]' nella stringa di una chiave nel percorso.
Capaj,

7
Questo. Inoltre, supporta_.set(...)
Josh C. il

cosa succede se l'objet non viene trovato?
DDave,

@DDave se il valore passato come oggetto non è definito o non è un oggetto, _.getmostrerà lo stesso comportamento di quando non viene trovata alcuna chiave nell'oggetto fornito. es _.get(null, "foo") -> undefined, _.get(null, "foo", "bar") -> "bar". Tuttavia, questo comportamento non è definito nei documenti, pertanto è soggetto a modifiche.
Ian Walker-Sperber,

5
@Capaj stai scherzando? E chi non vuole / non può usare lodash?
Andre Figueiredo,

74

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:

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

Con operatore di concatenamento opzionale :

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

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

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

Esempio da usare:

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

indennità :

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

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

Esempio:

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

Accedi all'array con [] :

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

Esempio:

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

2
Adoro questa tecnica. Questo è davvero disordinato ma volevo usare questa tecnica per il compito. let o = {a:{b:{c:1}}}; let str = 'a.b.c'; str.split('.').splice(0, str.split('.').length - 1).reduce((p,c)=>p&&p[c]||null, o)[str.split('.').slice(-1)] = "some new value";
rob-gordon,

1
Mi piace l'idea di utilizzare ridurre, ma la logica sembra fuori per 0, undefinede nullvalori. {a:{b:{c:0}}}restituisce nullinvece di 0. Forse il controllo esplicito di null o indefinito risolverà questi problemi. (p,c)=>p === undefined || p === null ? undefined : p[c]
SmujMaiku,

Ciao @SmujMaiku, la funzione "pronto per l'uso" ritorna correttamente per '0', 'undefined' e 'null', ho appena testato sulla console: resolPath ({a: {b: {c: 0}}}, ' abc ', null) => 0; Controlla se la chiave esiste al posto del valore stesso che evita più di un controllo
Adriano Spadoni,

qui defaultValue non funzionava, usando Reflect.has(o, k) ? ...(ES6 Reflect.has ) funzionava però
Andre Figueiredo

setPathimposterà il valore alla prima occorrenza dell'ultimo selettore. Ad esempio, let o = {}; setPath(o, 'a.a', 0)risulta {a: 0}invece che {a: {a: 0}}. vedi questo post per una soluzione.
pkfm,

62

Dovresti analizzare tu stesso la stringa:

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 la definizione di indici di array con notazione punti:

var part3name1 = "part3.0.name";

Rende più semplice l'analisi.

DEMO


@Felix Kling: la tua soluzione mi fornisce ciò di cui ho bisogno. E ti ringrazio molto per quello. Ma Alnitak offre anche modi diversi e sembra funzionare anche. Dal momento che posso scegliere solo una risposta, sceglierò la risposta Alnitak. Non che la sua soluzione sia migliore di te o qualcosa del genere. Ad ogni modo, apprezzo molto la tua soluzione e il tuo impegno.
Komaruloh,

1
@Komaruloh: Oh, ho pensato che tu potessi sempre votare le risposte sulla tua stessa domanda .... comunque stavo più o meno scherzando, non ho bisogno di più reputazione;) Buona codifica!
Felix Kling,

1
@Felix Kling: hai bisogno di almeno 15 reputazione per votare. :) Credo che non ti serva più reputazione con 69k +. Grazie
Komaruloh,

@Felix FWIW - la conversione dalla []sintassi alla sintassi delle proprietà è piuttosto banale.
Alnitak,

4
Se cambi il ciclo while in while (l > 0 && (obj = obj[current]) && i < l)allora questo codice funziona anche per le stringhe senza punti.
Snea,

39

Funziona anche per matrici / matrici all'interno dell'oggetto. Difensiva 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'])`);
<script src="https://cdn.rawgit.com/coderek/e7b30bac7634a50ad8fd/raw/174b6634c8f57aa8aac0716c5b7b2a7098e03584/js-test.js"></script>


10
Grazie, questa è la risposta migliore e più performante - jsfiddle.net/Jw8XB/1
Dominic

@Endless, vorrei sottolineare il percorso dovrebbe separare gli elementi con punti. Le parentesi graffe non funzioneranno. Vale a dire per accedere al primo elemento nella matrice utilizzare "0.sp asso".
TheZver

26

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 cautela quando si esercita il potere di eval. È un po 'come una sciabola leggera, se la accendi c'è una probabilità del 90% di recidere un arto. Non è per tutti.


7
Il fatto che eval sia o meno una buona idea dipende da dove provengono i dati della stringa di proprietà. Dubito che abbiate motivo di preoccuparvi per gli hacker che si sono introdotti in modo statico "var p = 'abc'; eval (p);" digita call. È un'idea perfettamente valida per questo.
James Wilkins,

17

È possibile ottenere il valore di un membro di oggetto profondo con notazione punto senza alcuna libreria JavaScript esterna con il semplice trucco seguente:

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

Nel tuo caso per ottenere il valore di part1.nameda someObjectsolo fare:

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

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


3
function deep_value (obj, path) {return new Function ('o', 'return o.' + path) (obj); }
ArcangelZith

14

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

  1. Sostituire [] sintassi della parentesi con.
  2. Dividersi . personaggio
  3. Rimuovi le stringhe vuote
  4. Trova il percorso (altrimenti undefined)

// "one liner" (ES6)

const deep_value = (obj, path) => 
  path
    .replace(/\[|\]\.?/g, '.')
    .split('.')
    .filter(s => s)
    .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


11

È una fodera 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


7

Penso che tu lo stia chiedendo:

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

Funzioneranno entrambi


O forse lo stai chiedendo

var partName = "part1";
var nameStr = "name";

var part1name = someObject[partName][nameStr];

Finalmente potresti chiedere questo

var partName = "part1.name";

var partBits = partName.split(".");

var part1name = someObject[partBits[0]][partBits[1]];

Penso che OP stia chiedendo l'ultima soluzione. Tuttavia, le stringhe non hanno Splitmetodo, ma piuttosto split.
duri

In realtà stavo chiedendo l'ultimo. La variabile partName è riempita con una stringa che indica la struttura chiave da valutare. La tua soluzione sembra avere senso. Tuttavia, potrebbe essere necessario modificare la profondità dei dati, ad esempio livello 4-5 e altro. E mi chiedo se posso trattare l'array e l'oggetto in modo uniforme con questo?
Komaruloh,

7

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

Opzione 1: dividere la stringa attivata. o [o] o 'o ", capovolgilo, 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 (la più veloce di tutte, tranne eval): scansione di caratteri di basso livello (nessuna regex / split / ecc., 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: ( novità : opzione 2 ampliata per supportare le virgolette - un po 'più lenta, 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 re (per quanto riguarda le prestazioni). Se hai percorsi di proprietà direttamente sotto il tuo controllo, non ci dovrebbero essere problemi con l'uso di 'eval' (specialmente se si desidera la velocità). Se trascini i percorsi delle proprietà "oltre il filo" ( sulla linea !? Lol: P), allora sì, usa qualcos'altro per essere sicuro. Solo un idiota direbbe di non usare mai "eval", poiché ci sono buoni motivi per usarlo. Inoltre, "Viene utilizzato nel parser JSON di Doug Crockford ." Se l'ingresso è sicuro, allora nessun problema. Usa lo strumento giusto per il lavoro giusto, tutto qui.


6

Per ogni evenienza, chiunque visiti questa domanda nel 2017 o successivamente e cerchi un modo facile da ricordare , ecco un elaborato post sul blog su Accesso agli oggetti nidificati in JavaScript senza essere confuso da

Impossibile leggere la proprietà "pippo" dell'errore non definito

Accedere agli oggetti nidificati utilizzando la matrice di riduzione

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 ad array nidificati, è possibile scrivere il proprio array ridurre util.

const getNestedObject = (nestedObj, pathArr) => {
    return pathArr.reduce((obj, key) =>
        (obj && obj[key] !== 'undefined') ? obj[key] : undefined, nestedObj);
}

// pass in your object structure as array elements
const name = getNestedObject(user, ['personalInfo', 'name']);

// to access nested array, just pass in array index as an element the path array.
const city = getNestedObject(user, ['personalInfo', 'address', 0, 'city']);
// this will return the city from the first address item.

Esiste anche un tipo eccellente che gestisce un tipo di 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.


6

AngularJS

L'approccio di Speigg è molto accurato e pulito, anche se ho trovato questa risposta mentre cercavo la soluzione di accesso alle proprietà dell'ambito AngularJS $ tramite percorso stringa e con una piccola modifica fa il lavoro:

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

Basta posizionare questa funzione nel controller di root e utilizzarla in qualsiasi ambito figlio come questo:

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

Vedere AngularJS ha$scope.$eval un altro modo per farlo con AngularJS.
Georg

3

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

È possibile utilizzare la notazione con punti, parentesi, indici numerici, proprietà dei numeri di stringa e chiavi 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


3
/**
 * 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")

3

Funzione semplice, che consente una stringa o un percorso di matrice.

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, 'a.b.c')); //foo

O

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

Se hai intenzione di pubblicare il codice come risposta, spiega perché il codice risponde alla domanda.
Tieson T.


2

Mentre ridurre è buono, sono sorpreso che nessuno abbia usato per ciascuno di essi:

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

Test


Non stai nemmeno verificando se obj [chiave] esiste davvero. Non è affidabile.
Carles Alcolea,

@CarlesAlcolea di default js non controllerà se esiste la chiave di un oggetto: a.b.cgenererà un'eccezione se non ci sono proprietà bnel tuo oggetto. Se hai bisogno di qualcosa che elimini silenziosamente il keypath sbagliato (che non consiglio), puoi comunque sostituire il forOach con questokeys.forEach((key)=> obj = (obj||{})[key]);
Flavien Volken,

Corro attraverso di esso un oggetto a cui mancava una parentesi graffa, mio ​​cattivo :)
Carles Alcolea,

1

Ho appena avuto la stessa domanda di recente e ho usato con successo https://npmjs.org/package/tea-properties che ancheset nidificato oggetti / matrici:

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

"Questo modulo è stato sospeso. Usa chaijs / pathval."
Patrick Fisher,

1

Ispirato dalla risposta di @ webjay: https://stackoverflow.com/a/46008856/4110122

Ho creato questa funzione che puoi usare per ottenere / impostare / annullare l'impostazione di qualsiasi valore nell'oggetto

function Object_Manager(obj, Path, value, Action) 
{
    try
    {
        if(Array.isArray(Path) == false)
        {
            Path = [Path];
        }

        let level = 0;
        var Return_Value;
        Path.reduce((a, b)=>{
            level++;
            if (level === Path.length)
            {
                if(Action === 'Set')
                {
                    a[b] = value;
                    return value;
                }
                else if(Action === 'Get')
                {
                    Return_Value = a[b];
                }
                else if(Action === 'Unset')
                {
                    delete a[b];
                }
            } 
            else 
            {
                return a[b];
            }
        }, obj);
        return Return_Value;
    }

    catch(err)
    {
        console.error(err);
        return obj;
    }
}

Per usarlo:

 // Set
 Object_Manager(Obj,[Level1,Level2,Level3],New_Value, 'Set');

 // Get
 Object_Manager(Obj,[Level1,Level2,Level3],'', 'Get');

 // Unset
 Object_Manager(Obj,[Level1,Level2,Level3],'', 'Unset');

1

Sto sviluppando un negozio online con React. Ho provato a modificare i valori nell'oggetto stato copiato per aggiornare lo stato originale con esso al momento dell'invio. Gli esempi sopra non hanno funzionato per me, perché la maggior parte di loro muta la struttura dell'oggetto copiato. Ho trovato un esempio funzionante della funzione per accedere e modificare i valori delle proprietà dell'oggetto annidato in profondità: https://lowrey.me/create-an-object-by-path-in-javascript-2/ Ecco qui:

const createPath = (obj, path, value = null) => {
  path = typeof path === 'string' ? path.split('.') : path;
  let current = obj;
  while (path.length > 1) {
    const [head, ...tail] = path;
    path = tail;
    if (current[head] === undefined) {
      current[head] = {};
    }
    current = current[head];
  }
  current[path[0]] = value;
  return obj;
};

1

Basato sulla risposta di Alnitak .

Ho avvolto il polyfill in un segno di spunta e ridotto la funzione a una riduzione a catena singola.

if (Object.byPath === undefined) {
  Object.byPath = (obj, path) => path
    .replace(/\[(\w+)\]/g, '.$1')
    .replace(/^\./, '')
    .split(/\./g)
    .reduce((ref, key) => key in ref ? ref[key] : ref, obj)
}

const data = {
  foo: {
    bar: [{
      baz: 1
    }]
  }
}

console.log(Object.byPath(data, 'foo.bar[0].baz'))


0

Se è necessario accedere a chiave nidificata diversa senza conoscerla al momento della codifica (sarà banale indirizzarle) è possibile utilizzare l'accessorio notazione array:

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

Sono equivalenti all'accessorio di notazione punti 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 alla tua domanda ...

MODIFICARE

Non userò una stringa per mantenere una sorta di query xpath per accedere a un valore di oggetto. Dato che devi chiamare una funzione per analizzare la query e recuperare il valore, seguirei 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 non ti senti a tuo agio 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 le controlla per te per errori di sintassi e così via.

A proposito, sento che un semplice incarico fatto al momento giusto sarà sufficiente ...


Interessante. Ma nel mio caso, someObject è ancora inizializzato quando assegno valore a part1name. Conosco solo la struttura. Ecco perché uso la stringa per descrivere la struttura. E sperando di poterlo usare per interrogare i miei dati da someObject. Grazie per aver condiviso il tuo pensiero. :)
Komaruloh,

@Komaruloh: Penso che scriveresti che l'oggetto NON è ancora inizializzato quando crei le tue variabili. A proposito, non capisco il punto, perché non puoi svolgere l'incarico al momento opportuno?
Eineki,

Ci dispiace non menzionare che someObject non è ancora inizializzato. Per quanto riguarda il motivo, someObject viene recuperato tramite il servizio Web. E voglio avere un array di header che consiste in part1name, part2qty, ecc. In modo da poter semplicemente passare in rassegna l'array di header e ottenere il valore desiderato in base al valore part1name come 'chiave' / percorso di someObject.
Komaruloh,

0

Le soluzioni qui sono solo per accedere alle chiavi profondamente annidate. Ne avevo bisogno per accedere, aggiungere, modificare ed eliminare 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 un array. Puoi sostituirlo con il tuo string_path.split(".").
  • type_of_function: 0 per l'accesso (non passare alcun valore a value), 0 per aggiunta e modifica. 1 per l'eliminazione.

0

Sulla base della 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


0

Invece di una stringa, è possibile utilizzare una matrice indirizzando oggetti e matrici nidificati, 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 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);

0

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

0

// (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"]));


0

Lavorare con Underscore' propertyo propertyOf:

var test = {
  foo: {
    bar: {
      baz: 'hello'
    }
  }
}
var string = 'foo.bar.baz';


// document.write(_.propertyOf(test)(string.split('.')))

document.write(_.property(string.split('.'))(test));
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore-min.js"></script>

In bocca al lupo...

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.