Доступ к вложенным объектам JavaScript со строковым ключом



Answers

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

Object.resolve = function(path, obj) {
    return path.split('.').reduce(function(prev, curr) {
        return prev ? prev[curr] : undefined
    }, obj || self)
}

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

Object.resolve("document.body.style.width")
// or
Object.resolve("style.width", document.body)
// or even use array indexes
// (someObject has been defined in the question)
Object.resolve("part3.0.size", someObject) 
// returns undefined when intermediate properties are not defined:
Object.resolve('properties.that.do.not.exist', {hello:'world'})

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

  • Невозможно использовать скобки ( [] ) для индексов массива (хотя указание индексов массива между периодами отлично работает, как показано выше)
  • Доступ к свойствам с периодами по их имени невозможен: {'my.favorite.number':42}
Question

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

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?




Вместо строки массив может использоваться для адресации вложенных объектов и массивов, например: ["my_field", "another_field", 0, "last_field", 10]

Вот пример, который изменил бы поле, основанное на этом представлении массива. Я использую что-то подобное в response.js для управляемых полей ввода, которые изменяют состояние вложенных структур.

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



Я думаю, вы просите об этом:

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

Вы могли бы просить об этом:

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

Оба из них будут работать

Или, может быть, вы просите об этом

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

var part1name = someObject[partName][nameStr];

Наконец, вы можете просить об этом

var partName = "part1.name";

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

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



ES6 : только одна строка в Vanila JS (она возвращает null, если не находит, а не дает ошибку):

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

Для готовой к использованию функции, которая также распознает false, 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) => o[p] = path.split('.').pop() === p ? 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)

Exemple

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



Построение ответа Алнитак:

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

}

Это также позволяет вам установить значение!

Я также создал пакет npm и github




Простая функция, позволяющая либо путь строки, либо массив.

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

ИЛИ

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



Если вам нужно получить доступ к разному вложенному ключу, не зная его во время кодирования (это будет тривиально для их адресации), вы можете использовать ассемблер нотации массива:

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

Они эквивалентны аксессуарам с точечной нотацией и могут варьироваться во время выполнения, например:

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

var part1name = someObject[part][property];

эквивалентно

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

или

var part1name = someObject.part1.name;

Надеюсь, это касается вашего вопроса ...

РЕДАКТИРОВАТЬ

Я не буду использовать строку для создания своего рода запроса xpath для доступа к значению объекта. Поскольку вам нужно вызвать функцию для синтаксического анализа запроса и получения значения, я буду следовать другому пути (не:

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

или, если вы не уверены в применении метода

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

Функции короче, яснее, интерпретатор проверяет их для синтаксических ошибок и так далее.

Кстати, я чувствую, что простое задание, сделанное в нужное время, будет достаточно ...




Вот тесты производительности для всех 4, причем победителями являются @TheZver и @Shanimal:

http://jsfiddle.net/Jw8XB/3/

Part 1
60
Part 3A
Object.byString: 2.536ms 
Part 1
60
Part 3A
getProperty: 0.274ms
Part 1
60
undefined
eval: 0.657ms
Part 1
60
Part 3A
path: 0.256ms



Я уверен, что я собираюсь проголосовать за это, но вот кратчайшая версия:

const resolve = (obj, path) => {
    try {
        return eval(`${obj}.${path}`);
    } catch (err) {
        return void 0;
    }
};



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

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

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

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

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




Как насчет этого решения:

setJsonValue: function (json, field, val) {
  if (field !== undefined){
    try {
      eval("json." + field + " = val");
    }
    catch(e){
      ;
    }
  }  
}

И этот, для получения:

getJsonValue: function (json, field){
  var value = undefined;
  if (field !== undefined) {
    try {
      eval("value = json." + field);
    } 
    catch(e){
      ;
    }
  }
  return value;
};

Вероятно, некоторые из них будут считать их небезопасными, но они должны быть намного быстрее, чем синтаксический анализ строки.




Это один лайнер с 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 версии w / уменьшить ...

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

Plunkr




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

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




С coffeescript вы можете использовать ? чтобы проверить, существует ли объект, и если да, следуйте цепочке следующим образом:

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

Если какое-либо свойство в цепочке не существует, значение будет неопределенным :-)




Хотя сокращение хорошо, я удивлен, что никто не использовал forEach:

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

Test




Links