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?


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.


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.

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'

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

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


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.


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/


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


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


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

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.


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: 'jack@dev.com',
    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.


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

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


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

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

Si vous souhaitez publier du code comme réponse, veuillez expliquer pourquoi le code répond à la question.
Tieson T.


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


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

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

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

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


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


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.

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


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

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

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

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

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.