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

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

Например, если у вас есть пустой объект test и вы хотите установить глубокую структуру без использования eval. например

test = {}
test.foo.name = "Hallo" // <<- foo is an Object
test.foo[3] = "Test" // <<- foo should remain as Object, not as Array 
test.foo.data[3].bar = 100 // <<- should not overwrite test.foo.name

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

Также доступен как jsfiddle: https://jsfiddle.net/gvaLzqqf/4/

Object.setValue = function(node, flatKey, value) {
    flatKey = flatKey.replace("[", ".");
    flatKey = flatKey.replace("]", "");
    var parts = flatKey.split(".")
    var oldNode = node
    parts.forEach(function(key, index) {
      if (/^\+?(0|[1-9]\d*)$/.test(key)) {
        key = key * 1
        if (index > 0) {
          var oldValue = parts[index - 1]
          if (!Array.isArray(oldNode[oldValue])) {
            oldNode[oldValue] = []
            node = oldNode[oldValue]
          }
        }
      }
      if (node[key] == undefined) {
        node[key] = {}
      }
      oldNode = node
      node = node[key]
    }); // for each
    oldNode[parts[parts.length - 1]] = value
    return oldNode[parts[parts.length - 1]]
  } // function

var test = {}
Object.setValue(test, "foo.name", "Mr. Foo")
Object.setValue(test, "foo.data[0].bar", 100)
Object.setValue(test, "and.another[2].deep", 20)

console.log("test = " + JSON.stringify(test))
console.log("test.foo.data[0].bar = " + test.foo.data[0].bar)

Как бы то ни было, есть ли лучший способ добиться этого?


person hhamm    schedule 19.10.2016    source источник


Ответы (2)


Вы можете разделить путь и уменьшить путь, пройдя данный объект. Если объект не существует, создайте новое свойство с именем или массивом. Позже присвойте значение.

function setValue(object, path, value) {
    var way = path.replace(/\[/g, '.').replace(/\]/g, '').split('.'),
        last = way.pop();

    way.reduce(function (o, k, i, kk) {
        return o[k] = o[k] || (isFinite(i + 1 in kk ? kk[i + 1] : last) ? [] : {});
    }, object)[last] = value;
}

var test = {};
setValue(test, "foo.name", "Mr. Foo");
setValue(test, "foo.data[0].bar", 100);
setValue(test, "and.another[2].deep", 20);
console.log(test);

person Nina Scholz    schedule 19.10.2016
comment
Именно то, что я искал! Спасибо! Это решение столь же элегантно, сколь и красиво. Однако одна вещь все еще отсутствует: массивы будут обрабатываться как объекты. Если вы запустите свое решение, вы увидите, что массивы используются как строковые ключи, а не целочисленные ключи. Следовательно, структура в конце неверна: вместо "key" : [ {}, {},... ] у вас есть "key" : {"0": {}, "1":{}... ] - person hhamm; 24.10.2016
comment
я добавил желаемую функцию для массива, но теперь вы получаете "another": [ undefined, undefined, { "deep": 20 }]. - person Nina Scholz; 24.10.2016
comment
Ух ты! Конечно, у вас будет undefined, так как это массив. Но именно этого и следовало ожидать. Большое спасибо, очень хорошее, красивое и легкое решение. - person hhamm; 26.10.2016

В этом случае я бы не стал изобретать велосипед и вместо этого использовал lodash. В частности, функция set(). По их примеру:

var object = { 'a': [{ 'b': { 'c': 3 } }] };

_.set(object, 'a[0].b.c', 4);
console.log(object.a[0].b.c);
// => 4

_.set(object, ['x', '0', 'y', 'z'], 5);
console.log(object.x[0].y.z);
// => 5
person Matt Way    schedule 19.10.2016
comment
Это решение работает, хотя оно не отвечает на вопрос. Я все равно решил принять решение, lodash не слишком тяжел, чтобы включить его в мой проект. Так что спасибо за решение. Однако решение Нины Шольц — это именно то, что я искал. - person hhamm; 24.10.2016
comment
В вашем вопросе прямо сказано: I am looking for a nice solution to access a property by string value и How ever, is there any better way to achieve this?. Я бы сказал, что я точно ответил на ваш вопрос, и на самом деле это правильный ответ. Использование lodash - это лучший способ решить вашу проблему, поскольку он делает именно то, что вам уже нужно, и не вызовет ошибок и т. д. В вашем вопросе не указано, что нельзя использовать уже проверенные библиотеки. - person Matt Way; 24.10.2016
comment
Мне жаль, что я могу отметить только одно решение как принятое. Решение Ninas — это то, что я искал, оно так же хорошо, как и ваше, с точки зрения удобства использования, но оно не требует добавления полной сторонней библиотеки. Я должен признать, что мой вопрос был недостаточно ясен, и что я должен был добавить, что предпочитаю не стороннее решение, если есть элегантный способ реализовать это. Однако решение Нины решает именно мою проблему и не делает больше. Вот почему я предпочитаю давать ей похвалу. - person hhamm; 26.10.2016