Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<blockquote> <p><strong>recent note:</strong> While I'm flattered that this answer has gotten many upvotes, I am also somewhat horrified. If one needs to convert dot-notation strings like "x.a.b.c" into references, it's probably a sign that there is something very wrong going on (unless maybe you're performing some strange deserialization). It is overkill because it is unnecessary metaprogramming, and also somewhat violates functional side-effect-free coding style. Also, expect massive performance hits as well if you do this more than you need to (e.g. as your app's default form of passing objects around and dereferencing them). If for some reason this is server-side js, the usual holds for sanitization of inputs. Novices who find their way to this answer should consider working with array representations instead, e.g. ['x','a','b','c'], or even something more direct/simple/straightforward if possible, like not losing track of the references themselves, or maybe some pre-existing unique id, etc.</p> </blockquote> <p>Here's an elegant one-liner that's 10x shorter than the other solutions:</p> <pre><code>function index(obj,i) {return obj[i]} 'a.b.etc'.split('.').reduce(index, obj) </code></pre> <p>[edit] Or in ECMAScript 6:</p> <pre><code>'a.b.etc'.split('.').reduce((o,i)=&gt;o[i], obj) </code></pre> <p>(Not that I think eval always bad like others suggest it is (though it usually is), nevertheless those people will be pleased that this method doesn't use eval. The above will find <code>obj.a.b.etc</code> given <code>obj</code> and the string <code>"a.b.etc"</code>.)</p> <p>In response to those who still are afraid of using <code>reduce</code> despite it being in the ECMA-262 standard (5th edition), here is a two-line recursive implementation:</p> <pre><code>function multiIndex(obj,is) { // obj,['1','2','3'] -&gt; ((obj['1'])['2'])['3'] return is.length ? multiIndex(obj[is[0]],is.slice(1)) : obj } function pathIndex(obj,is) { // obj,'1.2.3' -&gt; multiIndex(obj,['1','2','3']) return multiIndex(obj,is.split('.')) } pathIndex('a.b.etc') </code></pre> <p>Depending on the optimizations the JS compiler is doing, you may want to make sure any nested functions are not re-defined on every call via the usual methods (placing them in a closure, object, or global namespace).</p> <p><strong>edit</strong>:</p> <p>To answer an interesting question in the comments:</p> <blockquote> <p>how would you turn this into a setter as well? Not only returning the values by path, but also setting them if a new value is sent into the function? – Swader Jun 28 at 21:42</p> </blockquote> <p>(sidenote: sadly can't return an object with a Setter, as that would violate the calling convention; commenter seems to instead be referring to a general setter-style function with side-effects like <code>index(obj,"a.b.etc", value)</code> doing <code>obj.a.b.etc = value</code>.)</p> <p>The <code>reduce</code> style is not really suitable to that, but we can modify the recursive implementation:</p> <pre><code>function index(obj,is, value) { if (typeof is == 'string') return index(obj,is.split('.'), value); else if (is.length==1 &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); } </code></pre> <p>Demo:</p> <pre><code>&gt; obj = {a:{b:{etc:5}}} &gt; index(obj,'a.b.etc') 5 &gt; index(obj,['a','b','etc']) #works with both strings and lists 5 &gt; index(obj,'a.b.etc', 123) #setter-mode - third argument (possibly poor form) 123 &gt; index(obj,'a.b.etc') 123 </code></pre> <p>...though personally I'd recommend making a separate function <code>setIndex(...)</code>. I would like to end on a side-note that the original poser of the question could (should?) be working with arrays of indices (which they can get from <code>.split</code>), rather than strings; though there's usually nothing wrong with a convenience function.</p> <hr> <p>A commenter asked:</p> <blockquote> <p>what about arrays? something like "a.b[4].c.d[1][2][3]" ? –AlexS</p> </blockquote> <p>Javascript is a very weird language; in general objects can only have strings as their property keys, so for example if <code>x</code> was a generic object like <code>x={}</code>, then <code>x[1]</code> would become <code>x["1"]</code>... you read that right... yup...</p> <p>Javascript Arrays (which are themselves instances of Object) specifically encourage integer keys, even though you could do something like <code>x=[]; x["puppy"]=5;</code>.</p> <p>But in general (and there are exceptions), <code>x["somestring"]===x.somestring</code> (when it's allowed; you can't do <code>x.123</code>).</p> <p>(Keep in mind that whatever JS compiler you're using might choose, maybe, to compile these down to saner representations if it can prove it would not violate the spec.)</p> <p>So the answer to your question would depend on whether you're assuming those objects only accept integers (due to a restriction in your problem domain), or not. Let's assume not. Then a valid expression is a concatenation of a base identifier plus some <code>.identifier</code>s plus some <code>["stringindex"]</code>s</p> <p>This would then be equivalent to <code>a["b"][4]["c"]["d"][1][2][3]</code>, though we should probably also support <code>a.b["c\"validjsstringliteral"][3]</code>. You'd have to check the <a href="https://www.ecma-international.org/ecma-262/5.1/#sec-7.8.4" rel="noreferrer">ecmascript grammar section on string literals</a> to see how to parse a valid string literal. Technically you'd also want to check (unlike in my first answer) that <code>a</code> is a valid <a href="https://www.ecma-international.org/ecma-262/5.1/#sec-7.6" rel="noreferrer">javascript identifier</a>.</p> <p>A simple answer to your question though, <em>if your strings don't contain commas or brackets</em>, would be just be to match length 1+ sequences of characters not in the set <code>,</code> or <code>[</code> or <code>]</code>:</p> <pre><code>&gt; "abc[4].c.def[1][2][\"gh\"]".match(/[^\]\[.]+/g) // ^^^ ^ ^ ^^^ ^ ^ ^^^^^ ["abc", "4", "c", "def", "1", "2", ""gh""] </code></pre> <p><em>If your strings don't contain escape characters or <code>"</code> characters</em>, and because IdentifierNames are a sublanguage of StringLiterals (I think???) you could first convert your dots to []:</p> <pre><code>&gt; var R=[], demoString="abc[4].c.def[1][2][\"gh\"]"; &gt; for(var match,matcher=/^([^\.\[]+)|\.([^\.\[]+)|\["([^"]+)"\]|\[(\d+)\]/g; match=matcher.exec(demoString); ) { R.push(Array.from(match).slice(1).filter(x=&gt;x!==undefined)[0]); // extremely bad code because js regexes are weird, don't use this } &gt; R ["abc", "4", "c", "def", "1", "2", "gh"] </code></pre> <p>Of course, always be careful and never trust your data. Some bad ways to do this that might work for some use cases also include:</p> <pre><code>// hackish/wrongish; preprocess your string into "a.b.4.c.d.1.2.3", e.g.: &gt; yourstring.replace(/]/g,"").replace(/\[/g,".").split(".") "a.b.4.c.d.1.2.3" //use code from before </code></pre>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload