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

214

Данный объект JS

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

и строка

"a.b"

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

var val = obj.a.b

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

nevf
источник
25
@ Андрей evalзлой; не используйте это
kevinji
5
К вашему сведению: Вот несколько интересных тестов скорости, которые я только что сделал: jsperf.com/dereference-object-property-path-from-string
Джеймс Уилкинс,
если perf серьезное соображение и вы многократно используете одни и те же пути (например, внутри функции фильтра массива), используйте конструктор Function, как описано в моем ответе ниже. Когда один и тот же путь используется тысячи раз, метод Function может быть более чем в 10 раз быстрее, чем вычислять или разбивать, и сокращать путь при каждом разыменовании.
Кевин Крамли

Ответы:

437

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

То есть новички, которые находят путь к этому ответу, должны задать себе вопрос «почему я это делаю?»

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

Случай 1 : как основной метод работы с вашими данными (например, как форма вашего приложения по умолчанию для передачи объектов и разыменования их). Как, например, спросить «как я могу найти имя функции или переменной из строки».

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

Случай 2 : Работа с сериализованными данными или данными, которые будут отображаться пользователю. Как использование даты в качестве строки «1999-12-30», а не объекта Date (что может привести к ошибкам часового пояса или дополнительной сложности сериализации, если не соблюдать осторожность). Или ты знаешь, что делаешь.

  • Это может быть хорошо. Будьте осторожны, чтобы не было точечных строк "." в ваших санированных входных фрагментах.

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

Вот изящная однострочная, которая в 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.a.b.etcданная objстрока "a.b.etc")

В ответ на тех, кто все еще боится использовать, 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,"a.b.etc", value)выполнение obj.a.b.etc = 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["somestring"]===x.somestring(когда это разрешено; вы не можете сделать x.123).

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

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

Тогда это будет эквивалентно a["b"][4]["c"]["d"][1][2][3], хотя мы, вероятно, должны также поддержать a.b["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'], либо if instanceof Array, затем просто примите Array в качестве входных данных keys = ['a','b','c']; obj.H[keys].


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

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

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

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

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

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

ninjagecko
источник
4
reduceне поддерживается во всех используемых в настоящее время браузерах.
Рикардо Томази
14
@Ricardo: Array.reduceявляется частью стандарта ECMA-262. Если вы действительно хотите поддерживать устаревшие браузеры, вы можете определить Array.prototype.reduceпример реализации, приведенный где-то (например, developer.mozilla.org/en/JavaScript/Reference/Global_Objects/… ).
ninjagecko
2
Да, но достаточно просто поместить две строки в функцию. var setget = function( obj, path ){ function index( robj,i ) {return robj[i]}; return path.split('.').reduce( index, obj ); }
Nevf
3
Мне нравится этот элегантный пример, спасибо ninjagecko. Я расширил его, чтобы обработать нотацию стиля массива, а также пустые строки - посмотрите мой пример здесь: jsfiddle.net/sc0ttyd/q7zyd
Sc0ttyD
2
@ninjagecko, как бы ты превратил это в сеттер? Не только вернуть значения по пути, но и установить их, если новое значение отправлено в функцию?
Swader
64

Если вы можете использовать lodash , есть функция, которая делает именно это:

_.get (объект, путь, [defaultValue])

var val = _.get(obj, "a.b");
Петар иванов
источник
4
Примечание: _.get(object, path)не ломается, если путь не найден. 'a.b.etc'.split('.').reduce((o,i)=>o[i], obj)делает. Для моего конкретного случая - не для каждого случая - именно то, что мне нужно. Спасибо!
г-н Б.
1
@ Mr.B. последняя версия Lodash имеет третий необязательный аргумент для defaultValue. В _.get()методе возвращает значение по умолчанию , если _.get()решает , чтобы undefined, таким образом установить его на то , что вы хотите , и часов для установленного значения.
steampowered
7
Для всех, кто интересуется, это также поддерживает _.set(object, path, value).
Джеффри Розендал
19

Немного более сложный пример с рекурсией.

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
joekarl
источник
19

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

Вы просто устанавливаете этот пакет (npm i --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;
DarkCrazy
источник
10

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

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 уровня.

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

var setter = new Function("obj", "newval", "obj." + path + " = newval;");
setter(obj, "some new val");
Кевин Крамли
источник
1
если вам нужно разыменовать один и тот же путь на долгое время, ссылка на jsperf.com выше показывает пример того, как сохранить и просмотреть функцию позже. Вызов конструктора Function выполняется довольно медленно, поэтому высокопроизводительный код должен запоминать результаты, чтобы избежать повторения, если это возможно.
Кевин Крамли
9

Много лет со дня первоначального поста. Теперь есть отличная библиотека под названием «объект-путь». 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");

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

LPG
источник
7

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

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

Нина Шольц
источник
6

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

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

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

Тамлин
источник
1
Кажется, Underscore не поддерживает точечную запись. Ответ обновлен.
Тамлин
5

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

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] } }
Рикардо Томази
источник
5

Вы можете использовать библиотеку, доступную на 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!
Иеремия Сантос
источник
1
Спасибо, что сообщили нам об этом. Выглядит очень полезно.
nevf
И именно поэтому каждый проект внедряет 10000 зависимостей :-).
Марвин
4
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 .

McKayla
источник
Отлично, это работает удовольствие и короче, чем CD Sanchez. Спасибо.
Nevf
значение (a, 'bbbbb') не возвращает неопределенное значение
frumbert
4

Я расширил элегантный ответ с помощью 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/

Sc0ttyD
источник
Действительно хорошее решение. Есть только одна проблема, она предполагает, что []нотация всегда для массивов. также могут быть представлены ключи объектов, например, obj['some-problem/name'].list[1]чтобы исправить это, я должен был обновить arr_derefфункцию следующим образом javascript function arr_deref(o, ref, i) { return !ref ? o : (o[(ref.slice(0, i ? -1 : ref.length)).replace(/^['"]|['"]$/g, '')]); }
Серкан Йерсен,
1
Хотя в наше время я бы так не поступил. Я бы использовал Lodash: lodash.com/docs#get
Sc0ttyD
2
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
Кристиан Санчес
источник
Спасибо за все быстрые ответы. Не нравятся решения eval (). Этот и подобные посты выглядят лучше всего. Однако у меня все еще есть проблема. Я пытаюсь установить значение obj.ab = новое значение. Чтобы быть точным, значение b является функцией, поэтому мне нужно использовать obj.ab (new_value). Функция вызывается, но значение не установлено. Я думаю, что это проблема масштаба, но я все еще копаю. Я понимаю, что это выходит за рамки первоначального вопроса. Мой код использует Knockout.js, а b является ko.observable.
Nevf
@nevf: я добавил вторую функцию, которая, я думаю, выполняет то, что вы хотите. Вы можете настроить его по своему вкусу в зависимости от желаемого поведения (например, создавать ли объекты, если они не существуют? И т. Д.).
Кристиан Санчес
@nevf Но мой делает это с одной функцией. ; D
МакКейла
спасибо за обновление, которое мне удалось использовать. @tylermwashburn - и спасибо за вашу более короткую реализацию, которая также приносит удовольствие. Удачного всем ж / д.
Nevf
2

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

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, составляющие определение функции.

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

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

Хариш Анчу
источник
Вы можете точно объяснить, что это делает? И как бы вы передали 'ab' и т. Д. В качестве параметра функции?
невр
1
Я дал этому +1, но JSLint предупреждает, что «конструктор Function является формой eval» .
gabe
2

GET / SET ответ, который также работает в реагировать нативно (вы не можете назначить в 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
Александр Данилов
источник
1

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

Кроме того, это позволит нам делать то, что 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]
 }
Крис В.
источник
1

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


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

{
  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];
      }
    });

}
Мэтью Маллин
источник
Он не обрабатывал значения с "brothers.0.one", как JSON.
Картик
Есть ли у вас какие-либо предложения для обработки более сложных JSON с коллекцией массивов.?
Картик
0

Вот мой код без использования 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
Элвин Магалона
источник
0

Да, это было задано 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. Следующее расширение ломает мангуста в моем проекте. Также я читал, что это может сломать jquery. Итак, никогда не делай это следующим образом

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

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

    return result;
};
Майкл Капустей
источник
0

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

Реализация 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 (использование массива Reduce вместо Slice)

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'
Xeltor
источник
0

Это мое расширенное решение, предложенное: 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)
}
Yabree
источник
0

Рискуя побить мертвую лошадь ... Я считаю, что это наиболее полезно при обходе вложенных объектов для ссылки, где вы находитесь, относительно базового объекта или аналогичного объекта с такой же структурой. Для этого это полезно с функцией обхода вложенных объектов. Обратите внимание, что я использовал массив для хранения пути. Было бы тривиально изменить это, чтобы использовать либо путь строки, либо массив. Также обратите внимание, что вы можете присвоить значению «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);
}

srkleiman
источник
0

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

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
Prathak Paisanwatcharakit
источник
0

Несколько лет спустя я обнаружил, что это обрабатывает область и массив. например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
allenhwkim
источник
-1

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

var s = 'a.b';
s = 'obj["' + s.replace(/\./g, '"]["') + '"]';
alert(s); // displays obj["a"]["b"]
Марк Эйрих
источник
1
Это не работает a.b.c, и на самом деле не выполняет то, что они хотят .. Они хотят ценность, а не evalпуть.
Маккейла
Я теперь исправил это, чтобы он работал a.b.c, но вы правы, по-видимому, он хотел получить / установить значение свойства в obj.a.b. Вопрос сбил меня с толку, так как он сказал, что хочет «преобразовать строку» ....
Марк Эйрих
Хорошая работа. :) Это было немного расплывчато. Вы сделали хорошую работу по конвертации, хотя.
Маккейла
-1

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

Я проверил это с a.b.cи 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)
}
Zalaboza
источник