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

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

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

Вот изящная однострочная, которая в 10 раз короче, чем другие решения:

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

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

 'abetc'.split('.').reduce((o,i)={amp}gt;o[i], obj) 

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

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

 function multiIndex(obj,is) { // obj,['1','2','3'] -{amp}gt; ((obj['1'])['2'])['3'] return is.length ? multiIndex(obj[is[0]],is.slice(1)) : obj } function pathIndex(obj,is) { // obj,'1.2.3' -{amp}gt; multiIndex(obj,['1','2','3']) return multiIndex(obj,is.split('.')) } pathIndex('abetc') 

В зависимости от оптимизации, которую выполняет 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 {amp}amp;{amp}amp; value!==undefined) return obj[is[0]] = value; else if (is.length==0) return obj; else return index(obj[is[0]],is.slice(1), value); } 

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

 {amp}gt; obj = {a:{b:{etc:5}}} {amp}gt; index(obj,'abetc') 5 {amp}gt; index(obj,['a','b','etc']) #works with both strings and lists 5 {amp}gt; index(obj,'abetc', 123) #setter-mode - third argument (possibly poor form) 123 {amp}gt; index(obj,'abetc') 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 s плюс несколько ["stringindex"] s

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

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

 {amp}gt; "abc[4].c.def[1][2]["gh"]".match(/[^][.] /g) // ^^^ ^ ^ ^^^ ^ ^ ^^^^^ ["abc", "4", "c", "def", "1", "2", ""gh""] 

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

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

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

 // hackish/wrongish; preprocess your string into "ab4.cd1.2.3", eg: {amp}gt; yourstring.replace(/]/g,"").replace(/[/g,".").split(".") "ab4.cd1.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 ie 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)={amp}gt;o[i], obj); }, set: function(obj,key,value, proxy) { var keys = key.split('.'); var beforeLast = keys.slice(0,-1).reduce((o,i)={amp}gt;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['abc'] is:", objHyper['abc']); objHyper['abc'] = 3; console.log("(proxy override set) objHyper['abc']=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['abc']=4"); obj.H['abc'] = 4; console.log("(shortcut) obj.H['abc'] is obj['a']['b']['c'] is", obj.H['abc']); 

Выход:

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)={amp}gt;o===undefined?undefined:o[i] или (o,i)={amp}gt;(o||{})[i] .

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

Понравилась статья? Поделиться с друзьями:
JavaScript & TypeScript
Adblock
detector