684 votes

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

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

le nom de la partie 1 doit être rempli avec someObject.part1.name La valeur de part2quantity, qui est "Part 1". Même chose avec part2quantity qui s'est rempli de 60.

Est-il possible d'y parvenir avec du javascript pur ou du JQuery ?

0 votes

Je ne suis pas sûr de ce que vous demandez ici ? Vous voulez être capable d'interroger part1.name et d'obtenir le texte "part1.name" ? Ou vous voulez un moyen d'obtenir la valeur stockée dans part1.name ?

0 votes

Avez-vous essayé de faire comme var part1name = someObject.part1name; `

1 votes

@BonyT : Je veux interroger someObject.part1.name et retourner la valeur de celui-ci ("Part 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 votre réponse.

680voto

Alnitak Points 143355

J'ai juste fait ceci basé sur 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;
}

Utilisation : :

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

Voir une démo fonctionnelle à http://jsfiddle.net/alnitak/hEsys/

EDIT Certains ont remarqué que ce code déclenche une erreur si on lui passe une chaîne de caractères dont les index les plus à gauche ne correspondent pas à une entrée correctement imbriquée dans l'objet. Il s'agit d'une préoccupation valide, mais, à mon avis, il est préférable d'y répondre avec une fonction try / catch lors de l'appel, plutôt que d'avoir cette fonction retournant silencieusement undefined pour un index invalide.

0 votes

Votre réponse est exactement ce dont j'ai besoin. Elle fonctionne comme un charme. Cependant, Felix Kling a trouvé une autre solution qui fonctionne aussi bien que la vôtre. J'ai juste l'impression que je dois mentionner sa réponse comme étant la bonne. Quoi qu'il en soit, merci beaucoup pour la solution que vous m'avez donnée.

0 votes

Que se passe-t-il si la valeur est nulle ? Comment puis-je le gérer ?

42 votes

Cela fonctionne à merveille. Veuillez contribuer à l'Internet en l'emballant comme un paquet de nœuds.

273voto

speigg Points 81

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

Limitations :

  • On ne peut pas utiliser de parenthèses ( [] ) pour les indices de tableau - bien que la spécification des indices de tableau entre le jeton de séparation (par ex, . ) fonctionne bien, comme indiqué ci-dessus.

7 votes

Utiliser reduce est une excellente solution (on peut aussi utiliser _.reduce() de la bibliothèque underscore ou lodash)

5 votes

Je pense self est probablement indéfini ici. Voulez-vous dire this ?

0 votes

Mise en œuvre de lodash + typescript : _.reduce(path.split('.'), (previous, current) => !safe ? previous[current] : (previous ? previous[current] : undefined), target || self);

67voto

Felix Kling Points 247451

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 exigeait que vous définissiez également les index des tableaux avec la notation par points :

var part3name1 = "part3.0.name";

Cela rend l'analyse syntaxique plus facile.

DEMO

0 votes

@Felix Kling : Votre solution me fournit effectivement ce dont j'ai besoin. Et je vous remercie beaucoup pour cela. Mais Alnitak propose également différentes solutions qui semblent fonctionner également. Puisque je ne peux choisir qu'une seule réponse, je vais choisir celle d'Alnitak. Non pas que sa solution soit meilleure que la vôtre ou quelque chose comme ça. Quoi qu'il en soit, j'apprécie vraiment votre solution et les efforts que vous avez fournis.

0 votes

@Felix FWIW - conversion de [] à la syntaxe de la propriété est assez triviale.

0 votes

J'aime cette réponse parce que je peux donner à mes utilisateurs un format plus simple pour les chemins - en utilisant la notation par points pour les index au lieu des parenthèses. Merci !

43voto

TheZver Points 247

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>

11 votes

Merci, c'est la meilleure réponse et la plus performante. jsfiddle.net/Jw8XB/1

0 votes

@Endless, j'aimerais souligner que le chemin doit séparer les éléments avec des points. Les accolades ne fonctionnent pas. Par exemple, pour accéder au premier élément du tableau, utilisez "0.sp ace".

31voto

Shanimal Points 4067

En utilisant eval :

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

wrap pour retourner undefined 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 le pouvoir d'évaluation. C'est un peu comme un sabre laser, si vous l'allumez, il y a 90% de chances que vous vous coupiez un membre. Ce n'est pas pour tout le monde.

8 votes

Que l'évaluation soit une bonne idée ou non dépend de l'origine des données de la chaîne de propriété. Je doute que vous ayez une raison de vous inquiéter de l'intrusion de pirates via un appel statique du type "var p='a.b.c';eval(p) ;". C'est une idée parfaitement acceptable pour cela.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X