Преобразование строки JavaScript в точечной нотации в ссылку на объект

Учитывая объект JS

var obj = { a: { b: '1', c: '2' } }

и строка

"a.b"

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

var val = obj.a.b

Если бы строка была просто 'a', я мог бы использовать obj[a]. Но это сложнее. Я предполагаю, что есть какой-то простой метод, но в настоящее время он избегает.

Ответ 1

недавнее примечание: хотя я польщен, что этот ответ получил много голосов, я также несколько испуган. Если нужно преобразовать строки с точечными обозначениями, такие как "xabc", в ссылки, это, вероятно, признак того, что происходит что-то очень неправильное (если, возможно, вы не выполняете какую-то странную десериализацию). Это излишне, потому что это ненужное метапрограммирование, а также несколько нарушает функциональный стиль кодирования без побочных эффектов. Кроме того, ожидайте значительных падений производительности, если вы делаете это больше, чем нужно (например, в качестве формы приложения по умолчанию для передачи объектов и разыменования их). Если по какой-то причине это js на стороне сервера, то обычно выполняется для очистки входных данных. Новичкам, которые находят свой путь к этому ответу, следует вместо этого рассмотреть возможность работы с представлениями массивов, например, ['x', 'a', 'b', 'c'], или даже чем-то более прямым/простым/простым, если это возможно, например, не потерять отслеживание самих ссылок, или, возможно, какой-то ранее существовавший уникальный идентификатор и т.д.

Здесь элегантный однострочник, который в 10 раз короче других решений:

function index(obj,i) {return obj[i]}
'a.b.etc'.split('.').reduce(index, obj)

[править] Или в ECMAScript 6:

'a.b.etc'.split('.').reduce((o,i)=>o[i], obj)

(Не то, чтобы я думал, что eval всегда плох, как другие предполагают, что это так (хотя обычно это так), тем не менее, эти люди будут рады, что этот метод не использует eval. Выше приведено obj.abetc заданным obj и строкой "abetc".)

В ответ на тех, кто все еще боится использовать reduce несмотря на то, что он находится в стандарте ECMA-262 (5-е издание), вот рекурсивная реализация из двух строк:

function multiIndex(obj,is) {  // obj,['1','2','3'] -> ((obj['1'])['2'])['3']
    return is.length ? multiIndex(obj[is[0]],is.slice(1)) : obj
}
function pathIndex(obj,is) {   // obj,'1.2.3' -> multiIndex(obj,['1','2','3'])
    return multiIndex(obj,is.split('.'))
}
pathIndex('a.b.etc')

В зависимости от оптимизации, которую выполняет JS-компилятор, вы можете убедиться, что любые вложенные функции не переопределяются при каждом вызове с помощью обычных методов (помещая их в замыкание, объект или глобальное пространство имен).

редактировать:

Чтобы ответить на интересующий вопрос в комментариях:

как бы ты превратил это в сеттер? Не только вернуть значения по пути, но и установить их, если новое значение отправлено в функцию? - Swader 28 июня в 21:42

(sidenote: к сожалению, не может вернуть объект с помощью Setter, так как это нарушило бы соглашение о вызовах; вместо этого, кажется, что commenter ссылается на общую функцию стиля сеттера с побочными эффектами, такими как index(obj,"abetc", value) делать obj.abetc = value.)

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

function index(obj,is, value) {
    if (typeof is == 'string')
        return index(obj,is.split('.'), value);
    else if (is.length==1 && value!==undefined)
        return obj[is[0]] = value;
    else if (is.length==0)
        return obj;
    else
        return index(obj[is[0]],is.slice(1), value);
}

Демо-версия:

> obj = {a:{b:{etc:5}}}

> index(obj,'a.b.etc')
5
> index(obj,['a','b','etc'])   #works with both strings and lists
5

> index(obj,'a.b.etc', 123)    #setter-mode - third argument (possibly poor form)
123

> index(obj,'a.b.etc')
123

... хотя лично я бы рекомендовал сделать отдельную функцию setIndex(...). Я хотел бы закончить дополнительным замечанием, что первоначальный вопросник мог (должен?) Работать с массивами индексов (которые они могут получить из .split), а не со строками; хотя обычно нет ничего плохого с удобной функцией.


Комментатор спросил:

как насчет массивов? что-то вроде "ab [4].cd [1] [2] [3]"? -AlexS

Javascript - очень странный язык; в общем случае объекты могут иметь только строки в качестве ключей своих свойств, поэтому, например, если x был универсальным объектом, таким как x={}, тогда x[1] станет x["1"]... вы правильно прочитали... Ага...

Javascript Arrays (которые сами являются экземплярами Object) специально поддерживают целочисленные ключи, даже если вы могли бы сделать что-то вроде x=[]; x["puppy"]=5; x=[]; x["puppy"]=5; ,

Но в целом (и есть исключения), x["somestring"]===x.somestring (когда это разрешено; вы не можете сделать x.123).

(Имейте в виду, что любой JS-компилятор, который вы используете, может выбрать, возможно, скомпилировать их для более разумных представлений, если он может доказать, что он не будет нарушать спецификацию.)

Таким образом, ответ на ваш вопрос будет зависеть от того, предполагаете ли вы, что эти объекты принимают только целые числа (из-за ограничения в вашей проблемной области), или нет. Давайте предположим, что нет. Тогда допустимым выражением является объединение базового идентификатора плюс некоторый .identifier плюс несколько ["stringindex"] s

Тогда это будет эквивалентно a["b"][4]["c"]["d"][1][2][3], хотя мы, вероятно, должны также поддерживать ab["c\"validjsstringliteral"][3] Вы должны проверить раздел грамматики ecmascript для строковых литералов, чтобы увидеть, как анализировать действительный строковый литерал. Технически вы также хотели бы проверить (в отличие от моего первого ответа), что a является допустимым идентификатором javascript.

Хотя простой ответ на ваш вопрос, если ваши строки не содержат запятых или скобок, будет состоять в том, чтобы соответствовать последовательности 1+ символов, которых нет в наборе , или [ или ]:

> "abc[4].c.def[1][2][\"gh\"]".match(/[^\]\[.]+/g)
// ^^^ ^  ^ ^^^ ^  ^   ^^^^^
["abc", "4", "c", "def", "1", "2", ""gh""]

Если ваши строки не содержат escape-символов или " символов", и поскольку IdentifierNames являются подъязыком StringLiterals (я думаю???), вы можете сначала преобразовать свои точки в []:

> var R=[], demoString="abc[4].c.def[1][2][\"gh\"]";
> for(var match,matcher=/^([^\.\[]+)|\.([^\.\[]+)|\["([^"]+)"\]|\[(\d+)\]/g; 
      match=matcher.exec(demoString); ) {
  R.push(Array.from(match).slice(1).filter(x=>x!==undefined)[0]);
  // extremely bad code because js regexes are weird, don't use this
}
> R

["abc", "4", "c", "def", "1", "2", "gh"]

Конечно, всегда будьте осторожны и никогда не доверяйте своим данным. Некоторые плохие способы сделать это, которые могут работать для некоторых случаев использования, также включают в себя:

// hackish/wrongish; preprocess your string into "a.b.4.c.d.1.2.3", e.g.: 
> yourstring.replace(/]/g,"").replace(/\[/g,".").split(".")
"a.b.4.c.d.1.2.3"  //use code from before

Специальный 2018 год редактировать:

Отпустите полный круг и сделайте самое неэффективное, ужасно запрограммированное решение, которое мы можем придумать... в интересах синтаксической чистоты. С объектами ES6 Proxy!... Позвольте также определить некоторые свойства, которые (imho хорошо и замечательно, но) могут нарушать неправильно написанные библиотеки. Возможно, вам следует с осторожностью использовать это, если вы заботитесь о производительности, здравомыслии (своих или чужих), своей работе и т.д.

// [1,2,3][-1]==3 (or just use .slice(-1)[0])
if (![1][-1])
    Object.defineProperty(Array.prototype, -1, {get() {return this[this.length-1]}}); //credit to caub

// WARNING: THIS XTREME™ RADICAL METHOD IS VERY INEFFICIENT,
// ESPECIALLY IF INDEXING INTO MULTIPLE OBJECTS,
// because you are constantly creating wrapper objects on-the-fly and,
// even worse, going through Proxy i.e. runtime ~reflection, which prevents
// compiler optimization

// Proxy handler to override obj[*]/obj.* and obj[*]=...
var hyperIndexProxyHandler = {
    get: function(obj,key, proxy) {
        return key.split('.').reduce((o,i)=>o[i], obj);
    },
    set: function(obj,key,value, proxy) {
        var keys = key.split('.');
        var beforeLast = keys.slice(0,-1).reduce((o,i)=>o[i], obj);
        beforeLast[keys[-1]] = value;
    },
    has: function(obj,key) {
        //etc
    }
};
function hyperIndexOf(target) {
    return new Proxy(target, hyperIndexProxyHandler);
}

Демо-версия:

var obj = {a:{b:{c:1, d:2}}};
console.log("obj is:", JSON.stringify(obj));

var objHyper = hyperIndexOf(obj);
console.log("(proxy override get) objHyper['a.b.c'] is:", objHyper['a.b.c']);
objHyper['a.b.c'] = 3;
console.log("(proxy override set) objHyper['a.b.c']=3, now obj is:", JSON.stringify(obj));

console.log("(behind the scenes) objHyper is:", objHyper);

if (!({}).H)
    Object.defineProperties(Object.prototype, {
        H: {
            get: function() {
                return hyperIndexOf(this); // TODO:cache as a non-enumerable property for efficiency?
            }
        }
    });

console.log("(shortcut) obj.H['a.b.c']=4");
obj.H['a.b.c'] = 4;
console.log("(shortcut) obj.H['a.b.c'] is obj['a']['b']['c'] is", obj.H['a.b.c']);

Выход:

obj is: {"a": {"b": {"c": 1, "d": 2}}}

(получить переопределение прокси) objHyper ['abc']: 1

(набор переопределений прокси) objHyper ['abc'] = 3, теперь obj: {"a": {"b": {"c": 3, "d": 2}}}

(за кадром) objHyper - это: Прокси {a: {…}}

(ярлык) obj.H ['abc'] = 4

(ярлык) obj.H ['abc'] is obj ['a'] ['b'] ['c']: 4

неэффективная идея: Вы можете изменить вышеприведенное для отправки на основе входного аргумента; либо используйте метод .match(/[^\]\[.]+/g) для поддержки obj['keys'].like[3]['this'], либо если instanceof Array, то просто примите Array как введите как keys = ['a','b','c']; obj.H[keys] keys = ['a','b','c']; obj.H[keys].


По предположению, что, возможно, вы захотите обрабатывать неопределенные индексы более "мягким" образом в стиле NaN (например, index({a:{b:{c:...}}}, 'axc') возвращает undefined, а не uncaught TypeError)...:

1) Это имеет смысл с точки зрения "мы должны вернуть undefined вместо того, чтобы выбросить ошибку" в ситуации с 1-мерным индексом ({}) ['eg'] == undefined, поэтому "мы должны вернуть undefined вместо того, чтобы бросать ошибка "в N-мерной ситуации.

2) Это не имеет смысла с точки зрения того, что мы делаем x['a']['x']['c'], что приведет к ошибке TypeError в приведенном выше примере.

Тем не менее, вы бы сделали эту работу, заменив функцию сокращения на:

(o,i)=>o===undefined?undefined:o[i] или (o,i)=>(o||{})[i].

(Вы можете сделать это более эффективным, используя цикл for и прерывая/возвращая всякий раз, когда подрезультат, к которому будет добавлен следующий индекс, не определен, или используя try-catch, если вы ожидаете, что такие ошибки будут достаточно редкими.)

Ответ 3

Немного более привлекательный пример с рекурсией.

function recompose(obj,string){
    var parts = string.split('.');
    var newObj = obj[parts[0]];
    if(parts[1]){
        parts.splice(0,1);
        var newString = parts.join('.');
        return recompose(newObj,newString);
    }
    return newObj;
}


var obj = { a: { b: '1', c: '2', d:{a:{b:'blah'}}}};

alert(recompose(obj,'a.d.a.b')); //blah

Ответ 4

Вы также можете использовать lodash.get

Вы просто устанавливаете этот пакет (npm я --save lodash.get) и затем используете его следующим образом:

const get = require('lodash.get');

const myObj = { user: { firstName: 'Stacky', lastName: 'Overflowy' }, id: 123 };

console.log(get(myObj, 'user.firstName')); // prints Stacky
console.log(get(myObj, 'id')); //prints  123

//You can also update values
get(myObj, 'user').firstName = John;

Ответ 5

Если вы ожидаете разыменовать один и тот же путь много раз, построение функции для каждого точечного пути обозначений фактически имеет наилучшую производительность (расширение на перфекционных тестах Джеймса Уилкинса, связанных с комментариями выше).

var path = 'a.b.x';
var getter = new Function("obj", "return obj." + path + ";");
getter(obj);

Использование конструктора Function имеет некоторые те же недостатки, что и eval() с точки зрения безопасности и наихудшей производительности, но IMO - плохо используемый инструмент для случаев, когда вам нужна комбинация экстремального динамизма и высокой производительности. Я использую эту методологию для создания функций фильтра массива и вызова их внутри цикла дайджеста AngularJS. Мои профили последовательно показывают, что шаг array.filter() занимает менее 1 мс для разыменования и фильтрации около 2000 сложных объектов, используя динамически определенные пути 3-4 уровня в глубину.

Аналогичная методология может быть использована для создания функций setter, конечно:

var setter = new Function("obj", "newval", "obj." + path + " = newval;");
setter(obj, "some new val");

Ответ 6

Много лет со дня первоначального поста. Теперь есть отличная библиотека под названием "объект-путь". https://github.com/mariocasciaro/object-path

Доступно на NPM и BOWER https://www.npmjs.com/package/object-path

Это так же просто, как:

objectPath.get(obj, "a.c.1");  //returns "f"
objectPath.set(obj, "a.j.0.f", "m");

И работает для глубоко вложенных свойств и массивов.

Ответ 7

Другие предложения немного загадочны, поэтому я решил внести свой вклад:

Object.prop = function(obj, prop, val){
    var props = prop.split('.')
      , final = props.pop(), p 
    while(p = props.shift()){
        if (typeof obj[p] === 'undefined')
            return undefined;
        obj = obj[p]
    }
    return val ? (obj[final] = val) : obj[final]
}

var obj = { a: { b: '1', c: '2' } }

// get
console.log(Object.prop(obj, 'a.c')) // -> 2
// set
Object.prop(obj, 'a.c', function(){})
console.log(obj) // -> { a: { b: '1', c: [Function] } }

Ответ 8

Обратите внимание, что если вы уже используете Lodash, вы можете использовать property или get функции:

var obj = { a: { b: '1', c: '2' } };
_.property('a.b')(obj); // => 1
_.get(obj, 'a.b'); // => 1

У подчеркивания также есть функция property, но она не поддерживает точечную нотацию.

Ответ 9

var a = { b: { c: 9 } };

function value(layer, path, value) {
    var i = 0,
        path = path.split('.');

    for (; i < path.length; i++)
        if (value != null && i + 1 === path.length)
            layer[path[i]] = value;
        layer = layer[path[i]];

    return layer;
};

value(a, 'b.c'); // 9

value(a, 'b.c', 4);

value(a, 'b.c'); // 4

Это много кода по сравнению с гораздо более простым способом eval сделать это, но, как говорит Саймон Уиллисон, вы никогда не должны использовать eval.

Кроме того, JSFiddle.

Ответ 10

Я продлил изящный ответ ninjagecko, чтобы функция обрабатывала как пунктирные, так и/или массивные ссылки, и чтобы пустая строка заставляла возвращать родительский объект.

Здесь вы идете:

string_to_ref = function (object, reference) {
    function arr_deref(o, ref, i) { return !ref ? o : (o[ref.slice(0, i ? -1 : ref.length)]) }
    function dot_deref(o, ref) { return ref.split('[').reduce(arr_deref, o); }
    return !reference ? object : reference.split('.').reduce(dot_deref, object);
};

См. мой рабочий пример jsFiddle здесь: http://jsfiddle.net/sc0ttyd/q7zyd/

Ответ 11

Я предлагаю разделить путь, повторить его и уменьшить объект, который у вас есть. Это предложение работает со значением по умолчанию для отсутствующих свойств.

const getValue = (object, keys) => keys.split('.').reduce((o, k) => (o || {})[k], object);

console.log(getValue({ a: { b: '1', c: '2' } }, 'a.b'));
console.log(getValue({ a: { b: '1', c: '2' } }, 'foo.bar.baz'));

Ответ 12

var find = function(root, path) {
  var segments = path.split('.'),
      cursor = root,
      target;

  for (var i = 0; i < segments.length; ++i) {
   target = cursor[segments[i]];
   if (typeof target == "undefined") return void 0;
   cursor = target;
  }

  return cursor;
};

var obj = { a: { b: '1', c: '2' } }
find(obj, "a.b"); // 1

var set = function (root, path, value) {
   var segments = path.split('.'),
       cursor = root,
       target;

   for (var i = 0; i < segments.length - 1; ++i) {
      cursor = cursor[segments[i]] || { };
   }

   cursor[segments[segments.length - 1]] = value;
};

set(obj, "a.k", function () { console.log("hello world"); });

find(obj, "a.k")(); // hello world

Ответ 13

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

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

В вашем случае:

var obj = { a: { b: '1', c: '2' } }
var val = new Function('_', 'return _.a.b')(obj);

Чтобы сделать его простым, вы можете написать такую ​​функцию:

function objGet(obj, path){
    return new Function('_', 'return _.' + path)(obj);
}

Пояснение:

Конструктор Function создает новый объект Function. В JavaScript каждая функция фактически является объектом Function. Синтаксис для создания функции явно с помощью конструктора функций:

new Function ([arg1[, arg2[, ...argN]],] functionBody)

где arguments(arg1 to argN) должна быть строкой, которая соответствует допустимому идентификатору javaScript, а functionBody - строка, содержащая инструкции javaScript, содержащие определение функции.

В нашем случае мы берем преимущество тела функции string для извлечения элемента объекта с точечной нотацией.

Надеюсь, что это поможет.

Ответ 14

GET/SET ответ, который также работает в реагировать на native (вы не можете назначить Object.prototype в настоящее время):

Object.defineProperty(Object.prototype, 'getNestedProp', {
    value: function(desc) {
        var obj = this;
        var arr = desc.split(".");
        while(arr.length && (obj = obj[arr.shift()]));
        return obj;
    },
    enumerable: false
});

Object.defineProperty(Object.prototype, 'setNestedProp', {
    value: function(desc, value) {
        var obj = this;
        var arr = desc.split(".");
        var last = arr.pop();
        while(arr.length && (obj = obj[arr.shift()]));
        obj[last] = value;
    },
    enumerable: false
});

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

var a = { values: [{ value: null }] };
var b = { one: { two: 'foo' } };

a.setNestedProp('values.0.value', b.getNestedProp('one.two'));
console.log(a.values[0].value); // foo

Ответ 15

Вы можете использовать библиотеку, доступную на npm, что упрощает этот процесс. https://www.npmjs.com/package/dot-object

 var dot = require('dot-object');

var obj = {
 some: {
   nested: {
     value: 'Hi there!'
   }
 }
};

var val = dot.pick('some.nested.value', obj);
console.log(val);

// Result: Hi there!

Ответ 16

Я скопировал следующее из ответ Рикардо Томаси и изменил, чтобы также создать под-объекты, которые еще не существуют по мере необходимости. Это немного менее эффективно (более if и создание пустых объектов), но должно быть довольно хорошо.

Кроме того, это позволит нам сделать Object.prop(obj, 'a.b', false), где раньше мы не могли. К сожалению, он все равно не позволит нам назначить undefined... Не уверен, как это сделать еще.

/**
 * Object.prop()
 *
 * Allows dot-notation access to object properties for both getting and setting.
 *
 * @param {Object} obj    The object we're getting from or setting
 * @param {string} prop   The dot-notated string defining the property location
 * @param {mixed}  val    For setting only; the value to set
 */
 Object.prop = function(obj, prop, val){
   var props = prop.split('.'),
       final = props.pop(),
       p;

   for (var i = 0; i < props.length; i++) {
     p = props[i];
     if (typeof obj[p] === 'undefined') {
       // If we're setting
       if (typeof val !== 'undefined') {
         // If we're not at the end of the props, keep adding new empty objects
         if (i != props.length)
           obj[p] = {};
       }
       else
         return undefined;
     }
     obj = obj[p]
   }
   return typeof val !== "undefined" ? (obj[final] = val) : obj[final]
 }

Ответ 17

Если вы хотите преобразовать любой объект, содержащий ключи точечной нотации, в массив этих версий ключей, вы можете использовать это.


Это преобразует что-то вроде

{
  name: 'Andy',
  brothers.0: 'Bob'
  brothers.1: 'Steve'
  brothers.2: 'Jack'
  sisters.0: 'Sally'
}

в

{
  name: 'Andy',
  brothers: ['Bob', 'Steve', 'Jack']
  sisters: ['Sally']
}

convertDotNotationToArray(objectWithDotNotation) {

    Object.entries(objectWithDotNotation).forEach(([key, val]) => {

      // Is the key of dot notation 
      if (key.includes('.')) {
        const [name, index] = key.split('.');

        // If you have not created an array version, create one 
        if (!objectWithDotNotation[name]) {
          objectWithDotNotation[name] = new Array();
        }

        // Save the value in the newly created array at the specific index 
        objectWithDotNotation[name][index] = val;
        // Delete the current dot notation key val
        delete objectWithDotNotation[key];
      }
    });

}

Ответ 18

Вот мой код без использования eval. Его легко понять.

function value(obj, props) {
  if (!props) return obj;
  var propsArr = props.split('.');
  var prop = propsArr.splice(0, 1);
  return value(obj[prop], propsArr.join('.'));
}

var obj = { a: { b: '1', c: '2', d:{a:{b:'blah'}}}};

console.log(value(obj, 'a.d.a.b')); //returns blah

Ответ 19

Да, это было спрошено 4 года назад, и да, расширение базовых прототипов обычно не является хорошей идеей, но если вы сохраните все расширения в одном месте, они могут быть полезны. Итак, вот мой способ сделать это.

   Object.defineProperty(Object.prototype, "getNestedProperty", {
    value     : function (propertyName) {
        var result = this;
        var arr = propertyName.split(".");

        while (arr.length && result) {
            result = result[arr.shift()];
        }

        return result;
    },
    enumerable: false
});

Теперь вы сможете получить вложенное свойство везде без импорта модуля с функцией или функцией копирования/вставки.

UPD.Example:

{a:{b:11}}.getNestedProperty('a.b'); //returns 11

UPD 2. В моем проекте добавляется расширение mongoose. Также я читал, что это может сломать jquery. Итак, никогда не делайте этого в следующем порядке

 Object.prototype.getNestedProperty = function (propertyName) {
    var result = this;
    var arr = propertyName.split(".");

    while (arr.length && result) {
        result = result[arr.shift()];
    }

    return result;
};

Ответ 20

Вот моя реализация

Реализация 1

Object.prototype.access = function() {
    var ele = this[arguments[0]];
    if(arguments.length === 1) return ele;
    return ele.access.apply(ele, [].slice.call(arguments, 1));
}

Реализация 2 (используя сокращение массива вместо среза)

Object.prototype.access = function() {
    var self = this;
    return [].reduce.call(arguments,function(prev,cur) {
        return prev[cur];
    }, self);
}

<сильные > Примеры:

var myobj = {'a':{'b':{'c':{'d':'abcd','e':[11,22,33]}}}};

myobj.access('a','b','c'); // returns: {'d':'abcd', e:[0,1,2,3]}
myobj.a.b.access('c','d'); // returns: 'abcd'
myobj.access('a','b','c','e',0); // returns: 11

он также может обрабатывать объекты внутри массивов, как и для

var myobj2 = {'a': {'b':[{'c':'ab0c'},{'d':'ab1d'}]}}
myobj2.access('a','b','1','d'); // returns: 'ab1d'

Ответ 21

Это мое расширенное решение, предложенное: ninjagecko

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

index (obj, 'data.accounts [0].address [0].postcode');

/**
 * Get object by index
 * @supported
 * - arrays supported
 * - array indexes supported
 * @not-supported
 * - multiple arrays
 * @issues:
 *  index(myAccount, 'accounts[0].address[0].id') - works fine
 *  index(myAccount, 'accounts[].address[0].id') - doesnt work
 * @Example:
 * index(obj, 'data.accounts[].id') => returns array of id's
 * index(obj, 'data.accounts[0].id') => returns id of 0 element from array
 * index(obj, 'data.accounts[0].addresses.list[0].id') => error
 * @param obj
 * @param path
 * @returns {any}
 */
var index = function(obj, path, isArray?, arrIndex?){

    // is an array
    if(typeof isArray === 'undefined') isArray = false;
    // array index,
    // if null, will take all indexes
    if(typeof arrIndex === 'undefined') arrIndex = null;

    var _arrIndex = null;

    var reduceArrayTag = function(i, subArrIndex){
        return i.replace(/(\[)([\d]{0,})(\])/, (i) => {
            var tmp = i.match(/(\[)([\d]{0,})(\])/);
            isArray = true;
            if(subArrIndex){
                _arrIndex =  (tmp[2] !== '') ? tmp[2] : null;
            }else{
                arrIndex =  (tmp[2] !== '') ? tmp[2] : null;
            }
            return '';
        });
    }

    function byIndex(obj, i) {
        // if is an array
        if(isArray){
            isArray = false;
            i = reduceArrayTag(i, true);
            // if array index is null,
            // return an array of with values from every index
            if(!arrIndex){
                var arrValues = [];
                _.forEach(obj, (el) => {
                    arrValues.push(index(el, i, isArray, arrIndex));
                })
                return arrValues;
            }
            // if array index is specified
            var value = obj[arrIndex][i];
            if(isArray){
                arrIndex = _arrIndex;
            }else{
                arrIndex = null;
            }
            return value;
        }else{
            // remove [] from notation,
            // if [] has been removed, check the index of array
            i = reduceArrayTag(i, false);
            return obj[i]
        }
    }

    // reduce with byIndex method
    return path.split('.').reduce(byIndex, obj)
}

Ответ 22

В опасности избиения мертвой лошади... Я нахожу это наиболее полезным при обходе вложенных объектов ссылкой, где вы находитесь, относительно базового объекта или аналогичного объекта с той же структурой. Для этого полезно использовать функцию обхода объекта. Обратите внимание, что я использовал массив для хранения пути. Было бы тривиально изменить это, чтобы использовать либо строковый путь, либо массив. Также обратите внимание, что вы можете назначить значение "undefined", в отличие от некоторых других реализаций.

/*
 * Traverse each key in a nested object and call fn(curObject, key, value, baseObject, path)
 * on each. The path is an array of the keys required to get to curObject from
 * baseObject using objectPath(). If the call to fn() returns falsey, objects below
 * curObject are not traversed. Should be called as objectTaverse(baseObject, fn).
 * The third and fourth arguments are only used by recursion.
 */
function objectTraverse (o, fn, base, path) {
    path = path || [];
    base = base || o;
    Object.keys(o).forEach(function (key) {
        if (fn(o, key, o[key], base, path) && jQuery.isPlainObject(o[key])) {
            path.push(key);
            objectTraverse(o[key], fn, base, path);
            path.pop();
        }
    });
}

/*
 * Get/set a nested key in an object. Path is an array of the keys to reference each level
 * of nesting. If value is provided, the nested key is set.
 * The value of the nested key is returned.
 */
function objectPath (o, path, value) {
    var last = path.pop();

    while (path.length && o) {
        o = o[path.shift()];
    }
    if (arguments.length < 3) {
        return (o? o[last] : o);
    }
    return (o[last] = value);
}

Ответ 23

Я использовал этот код в своем проекте

const getValue = (obj, arrPath) => (
  arrPath.reduce((x, y) => {
    if (y in x) return x[y]
    return {}
  }, obj)
)

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

const obj = { id: { user: { local: 104 } } }
const path = [ 'id', 'user', 'local' ]
getValue(obj, path) // return 104

Ответ 24

Несколько лет спустя я обнаружил, что обрабатывает область и массив. например a['b']["c"].d.etc

function getScopedObj(scope, str) {
  let obj=scope, arr;

  try {
    arr = str.split(/[\[\]\.]/) // split by [,],.
      .filter(el => el)             // filter out empty one
      .map(el => el.replace(/^['"]+|['"]+$/g, '')); // remove string quotation
    arr.forEach(el => obj = obj[el])
  } catch(e) {
    obj = undefined;
  }

  return obj;
}

window.a = {b: {c: {d: {etc: 'success'}}}}

getScopedObj(window, `a.b.c.d.etc`)             // success
getScopedObj(window, `a['b']["c"].d.etc`)       // success
getScopedObj(window, `a['INVALID']["c"].d.etc`) // undefined

Ответ 25

Неясно, каков ваш вопрос. Учитывая ваш объект, obj.a.b даст вам "2" так же, как и он. Если вы хотите манипулировать строкой для использования скобок, вы можете сделать это:

var s = 'a.b';
s = 'obj["' + s.replace(/\./g, '"]["') + '"]';
alert(s); // displays obj["a"]["b"]

Ответ 26

здесь мои 10 центов к случаю, ниже функция будет получена/установлена в зависимости от предоставленного пути,.. конечно, вы можете улучшить его, удалить || и замените его на Object.hasOwnProperty если вы по ошибке заботитесь о ложных значениях,

я протестировал его с помощью abc и ab2.c {a:{b:[0,1,{c:7}]}} и его работ как для установки, так и для получения :).

cheerz

function helper(obj, path, setValue){
  const l = String(path).split('.');
  return l.reduce((o,i, idx)=>{
   if( l.length-idx===1)  { o[i] = setValue || o[i];return setValue ? obj : o[i];}
  o[i] = o[i] || {};
   return o[i];
  }, x)
}