Accès aux objets et tableaux JavaScript imbriqués par chemin de chaîne

454

J'ai une structure de données comme celle-ci:

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'
        }
    ]
};

Et je voudrais accéder aux données en utilisant ces variables:

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

part1name doit être rempli avec someObject.part1.namela valeur de, qui est "Part 1". Même chose avec part2quantity qui a rempli de 60.

Existe-t-il de toute façon pour y parvenir avec du javascript pur ou JQuery?

Komaruloh
la source
Vous ne savez pas ce que vous demandez ici? Vous voulez pouvoir interroger part1.name et avoir le texte "part1.name" retourné? Ou vous voulez un moyen d'obtenir la valeur stockée dans part1.name?
BonyT
avez-vous essayé de faire comme var part1name = someObject.part1name;`
Rafay
1
@BonyT: Je veux interroger someObject.part1.name et en renvoyer la valeur ("Partie 1"). Cependant, je veux que la requête (je l'ai appelée "la clé") soit stockée dans une variable 'part1name'. Merci pour votre réponse. @ 3nigma: je l'ai certainement fait. Mais ce n'est pas mon intention. Merci pour la réponse.
Komaruloh
1
dans la réponse en double, j'aime la réponse de fyr stackoverflow.com/questions/8817394/…
Steve Black

Réponses:

520

Je viens de faire cela en fonction d'un code similaire que j'avais déjà, il semble fonctionner:

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

Usage::

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

Voir une démonstration de travail sur http://jsfiddle.net/alnitak/hEsys/

MODIFIER certains ont remarqué que ce code génèrerait une erreur s'il passait une chaîne où les index les plus à gauche ne correspondaient pas à une entrée correctement imbriquée dans l'objet. Il s'agit d'une préoccupation valable, mais à mon humble avis, il est préférable de traiter avec un try / catchbloc lors de l'appel, plutôt que de voir cette fonction retourner silencieusement undefinedpour un index non valide.

Alnitak
la source
19
Cela fonctionne à merveille. Merci de contribuer à Internet en l'enveloppant sous forme de package de nœud.
t3dodson
14
@ t3dodson Je viens de le faire: github.com/capaj/object-resolve-path sachez simplement que cela ne fonctionne pas bien lorsque le nom de votre propriété contient «[]» en lui-même. Regex le remplacera par "." et cela ne fonctionne pas comme prévu
Capaj
20
super trucs; en utilisant la bibliothèque de lodash, on peut aussi le faire:_.get(object, nestedPropertyString);
ian
17
Cela se perdra probablement dans la mer des commentaires, mais cela génère des erreurs si vous essayez d'adresser une propriété qui n'existe pas. Alors 'part3[0].name.iDontExist'. L'ajout d'une vérification pour voir si oun objet figure dans le if incorrectif résout le problème. (La façon dont vous vous y prenez dépend de vous). Voir le violon mis à jour: jsfiddle.net/hEsys/418
ste2425
2
C'est tellement d'or. Nous avons une application basée sur la configuration et c'est un peu utile! Merci!
Christian Esperar
182

Voici la solution que j'utilise:

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

Exemple d'utilisation:

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

Limites:

  • Impossible d'utiliser des crochets ( []) pour les indices de tableau, bien que la spécification d'indices de tableau entre le jeton de séparation (par exemple, .) fonctionne correctement, comme indiqué ci-dessus.
speigg
la source
7
utiliser réduire est une excellente solution (on peut également utiliser à _.reduce()partir de la bibliothèque de soulignement ou de lodash)
Alp
3
Je pense que ce selfn'est probablement pas défini ici. Tu veux dire this?
Platinum Azure
2
Voici mon complément pour définir les valeurs par chemin: pastebin.com/jDp5sKT9
mroach
1
Quelqu'un sait comment le porter sur TypeScript?
Adam Plocher
1
@ SC1000 bonne idée. Cette réponse a été écrite avant que les paramètres par défaut ne soient disponibles dans la plupart des navigateurs. Je vais le mettre à jour pour "fonction résoudre (chemin, obj = self)", car référencer l'objet global comme défaut est intentionnel.
speigg
179

Ceci est maintenant supporté par lodash using _.get(obj, property). Voir https://lodash.com/docs#get

Exemple de la documentation:

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'
Ian Walker-Sperber
la source
9
Cela devrait être la seule réponse acceptée, car c'est la seule qui fonctionne à la fois pour la syntaxe des points et des crochets et elle n'échoue pas, lorsque nous avons «[]» dans la chaîne d'une clé dans le chemin.
Capaj
7
Cette. De plus, il prend en charge_.set(...)
Josh C.
que se passe-t-il si l'objet n'est pas trouvé?
DDave
@DDave si la valeur transmise car l'objet n'est pas défini ou n'est pas un objet, _.getaffichera le même comportement que lorsqu'aucune clé n'est trouvée dans l'objet fourni. par exemple _.get(null, "foo") -> undefined, _.get(null, "foo", "bar") -> "bar". Cependant, ce comportement n'est pas défini dans les documents, donc sujet à changement.
Ian Walker-Sperber
5
@Capaj vous plaisantez? Et qui ne veut pas / ne peut pas utiliser lodash?
Andre Figueiredo
74

ES6 : Une seule ligne dans Vanila JS (elle retourne null si ne pas trouver au lieu de donner une erreur):

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

Ou exemple:

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

Avec opérateur de chaînage en option :

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

Pour une fonction prête à l'emploi qui reconnaît également les nombres faux, 0 et négatif et accepte les valeurs par défaut comme paramètre:

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

Exemple à utiliser:

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

Bonus :

Pour définir un chemin (demandé par @ rob-gordon), vous pouvez utiliser:

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

Exemple:

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

Tableau d'accès avec [] :

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,'a.b[0].c') => 1
resolvePath(myVar,'a["b"][\'0\'].c') => 1
Adriano Spadoni
la source
2
J'adore cette technique. C'est vraiment compliqué mais je voulais utiliser cette technique pour l'affectation. 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
Je aime l'idée d'utiliser réduis mais votre logique semble hors de 0, undefinedet les nullvaleurs. {a:{b:{c:0}}}renvoie nullau lieu de 0. Peut-être que la recherche explicite de null ou d'undefined permettra de résoudre ces problèmes. (p,c)=>p === undefined || p === null ? undefined : p[c]
SmujMaiku
Salut @SmujMaiku, la fonction "prêt à l'emploi" renvoie correctement pour '0', 'non défini' et 'null', je viens de tester sur la console: resolPath ({a: {b: {c: 0}}}, ' abc ', null) => 0; Il vérifie si la clé existe au lieu de la valeur elle-même, ce qui évite plus d'un contrôle
Adriano Spadoni
ici defaultValue n'a pas fonctionné, en utilisant Reflect.has(o, k) ? ...(ES6 Reflect.has ) a bien fonctionné
Andre Figueiredo
setPathdéfinira la valeur à la première occurrence du dernier sélecteur. Par exemple, let o = {}; setPath(o, 'a.a', 0)résulte en {a: 0}plutôt qu'en {a: {a: 0}}. voir cet article pour une solution.
pkfm
62

Vous devez analyser la chaîne vous-même:

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

Cela nécessitait que vous définissiez également les index de tableau avec la notation par points:

var part3name1 = "part3.0.name";

Cela facilite l'analyse.

DEMO

Felix Kling
la source
@Felix Kling: Votre solution me fournit ce dont j'ai besoin. Et je vous en remercie beaucoup. Mais Alnitak propose également différentes méthodes et semble fonctionner non plus. Comme je ne peux choisir qu'une seule réponse, je choisirai la réponse Alnitak. Pas que sa solution soit meilleure que vous ou quelque chose comme ça. Quoi qu'il en soit, j'apprécie vraiment votre solution et vos efforts.
Komaruloh
1
@Komaruloh: Oh, je pensais que vous pouvez toujours voter pour les réponses à votre propre question .... de toute façon, je plaisantais plus, je n'ai pas besoin de plus de réputation;) Bon codage!
Felix Kling
1
@Felix Kling: Vous avez besoin d'au moins 15 points de réputation pour voter. :) Je crois que vous n'avez pas besoin de plus de réputation avec 69k +. Merci
Komaruloh
@Felix FWIW - la conversion de la []syntaxe en syntaxe de propriété est assez triviale.
Alnitak
4
Si vous changez la boucle while en while (l > 0 && (obj = obj[current]) && i < l)alors ce code fonctionne également pour les chaînes sans points.
Snea
39

Fonctionne également pour les tableaux / tableaux à l'intérieur de l'objet. Défensif contre les valeurs invalides.

/**
 * 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>

TheZver
la source
10
Merci, c'est la réponse la meilleure et la plus performante - jsfiddle.net/Jw8XB/1
Dominic
@Endless, je voudrais souligner que le chemin doit séparer les éléments par des points. Les accolades ne fonctionneront pas. Par exemple, pour accéder au premier élément du tableau, utilisez "0.sp ace".
TheZver
26

en utilisant eval:

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

envelopper pour retourner indéfini en cas d'erreur

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

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

Veuillez faire preuve de bon sens et de prudence lorsque vous utilisez la puissance de l'évaluation. C'est un peu comme un sabre laser, si vous l'allumez, il y a 90% de chances que vous coupiez un membre. Ce n'est pas pour tout le monde.

Shanimal
la source
7
Que eval soit une bonne idée ou non dépend de la provenance des données de la chaîne de propriétés. Je doute que vous ayez des raisons de vous inquiéter du piratage des intrus via un "var p = 'abc'; eval (p);" statique. " tapez appel. C'est une très bonne idée pour ça.
James Wilkins
17

Vous pouvez réussir à obtenir la valeur d'un membre d'objet profond avec la notation par points sans bibliothèque JavaScript externe avec la simple astuce suivante:

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

Dans votre cas pour obtenir une valeur de part1.namede le someObjectfaire:

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

Voici une démo de violon simple: https://jsfiddle.net/harishanchu/oq5esowf/

Harish Anchu
la source
3
function deep_value (obj, path) {return new Function ('o', 'return o.' + path) (obj); }
ArcangelZith
14

Cela ne verra probablement jamais le jour ... mais le voici quand même.

  1. Remplacer la []syntaxe des crochets par.
  2. Partager . caractère
  3. Supprimer les chaînes vides
  4. Trouvez le chemin (sinon 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

Nick Grealy
la source
11

C'est une doublure avec du 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"

Ou encore mieux ...

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

Ou version ES6 avec réduction ...

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

Plunkr

James
la source
7

Je pense que vous demandez ceci:

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

Vous pourriez demander ceci:

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

Qui fonctionneront tous les deux


Ou peut-être que vous demandez cela

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

var part1name = someObject[partName][nameStr];

Enfin, vous pourriez demander cela

var partName = "part1.name";

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

var part1name = someObject[partBits[0]][partBits[1]];
Hogan
la source
Je pense que OP demande la dernière solution. Cependant, les chaînes n'ont pas de Splitméthode, mais plutôt split.
duri
En fait, je demandais le dernier. La variable partName est remplie de chaîne indiquant la structure de clé à valeur. Votre solution semble logique. Cependant, je devrai peut-être modifier la profondeur des données, comme le niveau 4-5 et plus. Et je me demande si je peux traiter le tableau et l'objet uniformément avec ça?
Komaruloh
7

Ici, j'offre plus de moyens, qui semblent plus rapides à bien des égards:

Option 1: Split string activé. ou [ou] ou 'ou ", inversez-le, ignorez les éléments vides.

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

Option 2 (la plus rapide de toutes, sauf eval): Balayage de caractères de bas niveau (pas de regex / split / etc, juste un balayage rapide de caractères). Remarque: Celui-ci ne prend pas en charge les guillemets pour les index.

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)

Option 3: ( nouveau : option 2 étendue pour prendre en charge les devis - un peu plus lent, mais toujours rapide)

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 (...)" est toujours roi (en termes de performances). Si vous avez des chemins de propriété directement sous votre contrôle, l'utilisation de 'eval' ne devrait pas poser de problème (surtout si la vitesse est souhaitée). Si vous tirez des chemins de propriété "sur le fil" ( sur la ligne !? Lol: P), alors oui, utilisez autre chose pour être en sécurité. Seul un idiot dirait de ne jamais utiliser "eval", car il y a de bonnes raisons de l'utiliser. En outre, "Il est utilisé dans l'analyseur JSON de Doug Crockford ." Si l'entrée est sûre, aucun problème. Utilisez le bon outil pour le bon travail, c'est tout.

James Wilkins
la source
6

Au cas où quelqu'un visiterait cette question en 2017 ou plus tard et chercherait un moyen facile à retenir , voici un article de blog élaboré sur l' accès aux objets imbriqués en JavaScript sans être embrouillé par

Impossible de lire la propriété 'foo' d'une erreur non définie

Accéder aux objets imbriqués à l'aide de Array Reduce

Prenons cet exemple de structure

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

Pour pouvoir accéder aux tableaux imbriqués, vous pouvez écrire votre propre utilitaire de réduction de tableau.

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.

Il existe également un excellent type de gestion de type de bibliothèque minimale qui fait tout cela pour vous.

Avec typy, votre code ressemblera à ceci

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

Avertissement: je suis l'auteur de ce package.

Dinesh Pandiyan
la source
6

AngularJS

L'approche de Speigg est très soignée et propre, bien que j'ai trouvé cette réponse en recherchant la solution d'accès aux propriétés de portée AngularJS $ par chemin de chaîne et avec une petite modification, elle fait le travail:

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

Placez simplement cette fonction dans votre contrôleur racine et utilisez-la dans n'importe quelle portée enfant comme ceci:

$scope.resolve( 'path.to.any.object.in.scope')
nesinervink
la source
Voir AngularJS a$scope.$eval pour une autre façon de le faire avec AngularJS.
georgeawg
3

Je n'ai pas encore trouvé de package pour effectuer toutes les opérations avec un chemin de chaîne, j'ai donc fini par écrire mon propre petit package rapide qui prend en charge insert (), get () (avec retour par défaut), set () et remove ( ) opérations.

Vous pouvez utiliser la notation par points, les crochets, les indices de nombre, les propriétés de nombre de chaînes et les clés avec des caractères non-mot. Utilisation simple ci-dessous:

> 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

Kyle
la source
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;
}

Marche avec

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")
Vitim.us
la source
3

Fonction simple, permettant soit une chaîne soit un chemin de tableau.

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

OU

console.log(get(obj, ['a', 'b', 'c'])); //foo
Ben
la source
Si vous souhaitez publier du code comme réponse, veuillez expliquer pourquoi le code répond à la question.
Tieson T.
2

Il existe npmmaintenant un module pour ce faire: https://github.com/erictrinh/safe-access

Exemple d'utilisation:

var access = require('safe-access');
access(very, 'nested.property.and.array[0]');
caleb
la source
2

Bien que réduire soit bon, je suis surpris que personne n'ait utilisé pour chacun:

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

Tester

Flavien Volken
la source
Vous ne vérifiez même pas si obj [clé] existe réellement. Ce n'est pas fiable.
Carles Alcolea
@CarlesAlcolea par défaut, js ne vérifiera pas non plus si la clé d'un objet existe: a.b.cdéclenchera une exception s'il n'y a pas de propriété bdans votre objet. Si vous avez besoin de quelque chose rejetant silencieusement le mauvais chemin de clavier (ce que je ne recommande pas), vous pouvez toujours remplacer le forEach par celui-cikeys.forEach((key)=> obj = (obj||{})[key]);
Flavien Volken
Je traverse un objet auquel il manquait une attelle bouclée, ma mauvaise :)
Carles Alcolea
1

Je viens de poser la même question récemment et j'ai utilisé avec succès https://npmjs.org/package/tea-properties qui a également setimbriqué des objets / tableaux:

avoir:

var o = {
  prop: {
    arr: [
      {foo: 'bar'}
    ]
  }
};

var properties = require('tea-properties');
var value = properties.get(o, 'prop.arr[0].foo');

assert(value, 'bar'); // true

ensemble:

var o = {};

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

assert(o.prop.arr[0].foo, 'bar'); // true
abernier
la source
"Ce module n'est plus disponible. Utilisez chaijs / pathval."
Patrick Fisher
1

Inspiré par la réponse de @ webjay: https://stackoverflow.com/a/46008856/4110122

J'ai fait cette fonction que vous pouvez utiliser pour obtenir / définir / annuler la valeur de n'importe quel objet

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

Pour l'utiliser:

 // 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');
Mohamad Hamouday
la source
1

Je développe une boutique en ligne avec React. J'ai essayé de changer les valeurs dans l'objet d'état copié pour mettre à jour l'état d'origine avec lui lors de la soumission. Les exemples ci-dessus n'ont pas fonctionné pour moi, car la plupart d'entre eux modifient la structure de l'objet copié. J'ai trouvé un exemple de travail de la fonction pour accéder et modifier les valeurs des propriétés des objets imbriqués profonds: https://lowrey.me/create-an-object-by-path-in-javascript-2/ La voici:

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;
};
Dm Mh
la source
1

D'après la réponse d'Alnitak .

J'ai enveloppé le polyfill dans un chèque et réduit la fonction à une seule réduction enchaînée.

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

M. Polywhirl
la source
0

Si vous devez accéder à différentes clés imbriquées sans le savoir au moment du codage (il sera trivial de les résoudre), vous pouvez utiliser l'accesseur de notation de tableau:

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

Ils sont équivalents à l'accesseur de notation par points et peuvent varier lors de l'exécution, par exemple:

var part = 'part1';
var property = 'name';

var part1name = someObject[part][property];

est équivalent à

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

ou

var part1name = someObject.part1.name;

J'espère que cela répondra à votre question ...

ÉDITER

Je n'utiliserai pas de chaîne pour conserver une sorte de requête xpath pour accéder à une valeur d'objet. Comme vous devez appeler une fonction pour analyser la requête et récupérer la valeur, je suivrais un autre chemin (pas:

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

ou, si vous n'êtes pas à l'aise avec la méthode d' application

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

Les fonctions sont plus courtes, plus claires, l'interprète les vérifie pour vous pour les erreurs de syntaxe et ainsi de suite.

Soit dit en passant, j'estime qu'une simple mission faite au bon moment sera suffisante ...

Eineki
la source
Intéressant. Mais dans mon cas, le someObject est encore initialisé lorsque j'attribue une valeur à part1name. Je connais seulement la structure. C'est pourquoi j'utilise une chaîne pour décrire la structure. Et en espérant pouvoir l'utiliser pour interroger mes données de someObject. Merci d'avoir partagé votre pensée. :)
Komaruloh
@Komaruloh: Je pense que vous écririez que l'objet n'est PAS encore initialisé lorsque vous créez vos variables. Soit dit en passant, je ne comprends pas pourquoi vous ne pouvez pas faire la mission au bon moment?
Eineki
Désolé de ne pas mentionner que someObject n'est pas encore initialisé. Quant à la raison, someObject est récupéré via le service Web. Et je veux avoir un tableau d'en-tête qui se compose de part1name, part2qty, etc. Pour que je puisse simplement parcourir le tableau d'en-tête et obtenir la valeur que je voulais en fonction de la valeur de part1name comme «clé» / chemin d'accès à someObject.
Komaruloh
0

Les solutions ici sont juste pour accéder aux clés profondément imbriquées. J'en avais besoin pour accéder, ajouter, modifier et supprimer les clés. Voici ce que j'ai trouvé:

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: chemin dans un tableau. Vous pouvez le remplacer par votre string_path.split(".").
  • type_of_function: 0 pour l'accès (ne transmettez aucune valeur à value), 0 pour l'ajout et la modification. 1 pour supprimer.
ayushgp
la source
0

S'appuyant sur la réponse d'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;
  };

}

Cela vous permet également de définir une valeur!

J'ai aussi créé un paquet npm et github avec ça

Tamb
la source
0

Au lieu d'une chaîne, un tableau peut être utilisé pour adresser des objets et des tableaux imbriqués, par exemple: ["my_field", "another_field", 0, "last_field", 10]

Voici un exemple qui changerait un champ basé sur cette représentation de tableau. J'utilise quelque chose comme ça dans react.js pour les champs d'entrée contrôlés qui changent l'état des structures imbriquées.

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);
Jodo
la source
0

Sur la base d'une réponse précédente, j'ai créé une fonction qui peut également gérer les crochets. Mais pas de points à l'intérieur en raison de la scission.

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);
}
Vincent
la source
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"]));

Oboo Cheng
la source
0

Travailler avec Underscore's propertyou 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>

Bonne chance...

Akash
la source