Доступ к вложенным объектам JavaScript с помощью строкового ключа - PullRequest
378 голосов
/ 27 июня 2011

У меня есть такая структура данных:

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

И я хотел бы получить доступ к данным, используя эти переменные:

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

part1name должно быть заполнено значением someObject.part1.name, которое является «Часть 1». То же самое с part2quantity, который заполнен 60.

Есть ли способ достичь этого с помощью чистого JavaScript или JQuery?

Ответы [ 31 ]

473 голосов
/ 27 июня 2011

Я только что сделал это на основе какого-то похожего кода, который у меня уже был, похоже, он работает:

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

Использование ::

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

См. Рабочую демонстрацию на http://jsfiddle.net/alnitak/hEsys/

EDIT некоторые заметили, что этот код выдаст ошибку, если передать строку, где крайние левые индексы не соответствуют правильно вложенной записи в объекте.Это действительная проблема, но IMHO лучше всего решать с блоком try / catch при вызове, вместо того, чтобы эта функция молча возвращала undefined для неверного индекса.

156 голосов
/ 02 марта 2014

Это решение, которое я использую:

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

Пример использования:

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

Ограничения:

  • Невозможно использовать скобки ([]) для индексов массива, хотя указание индексов массива между токеном-разделителем (например, .) работает нормально, как показано выше.
151 голосов
/ 08 июля 2015

Это теперь поддерживается lodash с использованием _.get(obj, property).См. https://lodash.com/docs#get

Пример из документов:

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'
61 голосов
/ 27 июня 2011

Вы должны проанализировать строку самостоятельно:

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

Для этого также необходимо определить индексы массива с точечной нотацией:

var part3name1 = "part3.0.name";

Это облегчает анализ.

DEMO

39 голосов
/ 08 мая 2017

ES6 : только одна строка в Vanila JS (вместо ошибки выдается ноль, если не найдено):

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

или пример:

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

Для готовой к использованию функции, которая также распознает ложное, 0 и отрицательное число и принимает значения по умолчанию в качестве параметра:

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

Пример использования:

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

Бонус :

Чтобы установить путь (запрашивается @ rob-gordon), который вы можете использовать:

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

Пример:

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

Массив доступа с [] :

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

Пример

const myVar = {a:{b:[{c:1}]}}
resolvePath(myVar,'a.b[0].c') => 1
resolvePath(myVar,'a["b"][\'0\'].c') => 1
39 голосов
/ 24 апреля 2013

Работает также для массивов / массивов внутри объекта.Защита от недопустимых значений.

/**
 * 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>
21 голосов
/ 20 декабря 2012

с использованием eval:

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

перенос, чтобы вернуть неопределенное значение при ошибке

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

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

Пожалуйста, используйте здравый смысл и осторожность при использованиисила Eval.Это немного похоже на легкую саблю, если вы включите ее, есть 90% -ный шанс, что вы отрежете конечность.Это не для всех.

16 голосов
/ 11 апреля 2015

Вы можете получить значение элемента глубокого объекта с точечной нотацией без какой-либо внешней библиотеки JavaScript с помощью простого трюка:

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

В вашем случае для получения значения part1.name из someObject просто сделайте:

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

Вот простая демонстрация скрипки: https://jsfiddle.net/harishanchu/oq5esowf/

9 голосов
/ 26 июня 2018

Это, вероятно, никогда не увидит свет дня ... но здесь, в любом случае.

  1. Заменить [] синтаксис скобки на .
  2. Разделить на . символ
  3. Удалить пустые строки
  4. Найти путь (в противном случае 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
8 голосов
/ 08 декабря 2016

Это один лайнер с 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"

Или даже лучше ...

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

Или версия ES6 с уменьшением ...

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

Plunkr

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...