/    Sign up×
Community /Pin to ProfileBookmark

retrieve value from multi-dim associative array using only one string as the key

Hi All

Lets assume the following object

[CODE]
var x = {
y: {
z: 10
}
} ;
[/CODE]

Next, I need to retrieve the value ’10’, but the only thing I have is one string as a key
For example (this doesn’t work):

[CODE]
var str = ‘[y][z]’ ; // or ‘y.z’??
alert(x[str]) ; // should alert 10
[/CODE]

Can this be done using a single string as input for x ?

cheers

UPDATE: a solution which comes close to what I was looking for is

[CODE]
var str = “x[‘y’][‘z’]” ;
alert(eval(str));
[/CODE]

to post a comment
JavaScript

25 Comments(s)

Copy linkTweet thisAlerts:
@JMRKERJul 20.2011 — Have you tried...
<i>
</i>&lt;!DOC HTML&gt;
&lt;html&gt;
&lt;head&gt;
&lt;title&gt; Untitled &lt;/title&gt;
&lt;script type="text/javascript"&gt;
var x = {
y: {
z: 10
}
};
alert(x.y.z);
&lt;/script&gt;

&lt;/head&gt;
&lt;body&gt;

&lt;/body&gt;
&lt;/html&gt;
Copy linkTweet thisAlerts:
@jeanlucaauthorJul 20.2011 — Sure, that works like a charm, but thats not the issue here.

You don't know about 'y.z' because they're stored in a string! So you could do

[CODE]
var str = 'y.z' ;
var items = 'y.z'.split() ;
alert( x[items[0]][items[1]] ;
[/CODE]

But that just doesn't look right (BTW: the code above was not tested)

cheers
Copy linkTweet thisAlerts:
@JMRKERJul 20.2011 — I guess I'm confused then...?

How would you know 'y' or 'z' is available if you do not know the structure of 'x'?

BTW: One reason the code would not work because a ')' is missing at the end of the alert.
Copy linkTweet thisAlerts:
@jeanlucaauthorJul 20.2011 — thats right, but you got the idea!

Think of it as a user provided you (through a text input field) [B]y.z[/B]
Copy linkTweet thisAlerts:
@Declan1991Jul 20.2011 — Well, you could eval it, but I wouldn't recommend that if the user is providing it, so instead, I'd parse the string, splitting it at ".", and then finding the corresponding element.

Something like:var x = {y:{z:10}};
var a = "y.z";
a = a.split(".");
var r = x;
for (var i = 0; i &lt; a.length; i++) {
r = r[a[i]];
}
Copy linkTweet thisAlerts:
@jeanlucaauthorJul 20.2011 — ok. I noticed that you can do the same with [B]new Function[/B]. Is this basically the same thing ?
Copy linkTweet thisAlerts:
@Declan1991Jul 20.2011 — You mean, new Function("x"+userString) where userString = ".y.z"? That's the same as eval, and a bad idea if you are taking the string from the user. If at all possible, avoid eval, new Function and setTimeout/Interval with strings.
Copy linkTweet thisAlerts:
@jeanlucaauthorJul 20.2011 — ok, thanks!
Copy linkTweet thisAlerts:
@JMRKERJul 20.2011 — Well, you could eval it, but I wouldn't recommend that if the user is providing it, so instead, I'd parse the string, splitting it at ".", and then finding the corresponding element.

Something like:var x = {y:{z:10}};
var a = "y.z";
a = a.split(".");
var r = x;
for (var i = 0; i &lt; a.length; i++) {
r = r[a[i]];
}
[/QUOTE]


I'm still a bit confused as to where you would use something like this. ?

Why would you need a loop when you already have been told there is 'y.x'?
<i>
</i>&lt;script type="text/javascript"&gt;
var x = {y:{z:10}};
var a = "y.z";
a = a.split(".");
alert(x[a[0]][a[1]]+' is same as '+x.y.z);
&lt;/script&gt;
Copy linkTweet thisAlerts:
@Declan1991Jul 20.2011 — Why would you need a loop when you already have been told there is 'y.x'?[/QUOTE]
I've a penchant for generalisation! Can't help it.
Copy linkTweet thisAlerts:
@jeanlucaauthorJul 21.2011 — exactly, suppose you don't know anything about the structure of [B]x[/B] and someone (or a webservice) tells to get a value out of the variable and the path is 'm.n.o.p'
Copy linkTweet thisAlerts:
@JMRKERJul 21.2011 — exactly, suppose you don't know anything about the structure of [B]x[/B] and someone (or a webservice) tells to get a value out of the variable and the path is 'm.n.o.p'[/QUOTE]

I'm not questioning the file structure.

I'm not questioning the fact that the creator would give you 'm.n.o.p' to access a variable.

I'm questioning why you would need to split the 'm.n.o.p' into an array

when you could use the arrayX.m.n.o.p access directly. ?
Copy linkTweet thisAlerts:
@jamesbcox1980Jul 21.2011 — I think I see what you're looking for. You need quotes around y and z when used in brackets, otherwise you're telling the javascript to eval a variable. Also, x must be in a string since the brackets are in strings, and these two must be concatenated:

[CODE]var str = '["y"]["z"]',
x = {
y : {
z : 10
}
};

alert(eval('x' + str)) ; // should alert 10[/CODE]


Or using the x.y.z structure:

[CODE]var str = '.' + 'y.z', //notice I add a leading dot
x = {
y : {
z : 10
}
};

alert(eval('x' + str)) ; // should alert 10[/CODE]


This assumes you know the name of x and aren't using variables created on the fly or chosen through conditions.
Copy linkTweet thisAlerts:
@Declan1991Jul 21.2011 — I'm questioning why you would need to split the 'm.n.o.p' into an array

when you could use the arrayX.m.n.o.p access directly. ?[/QUOTE]

I thought the whole point was that the m.n.o.p was coming from the user, and I wouldn't be in an awful rush to eval stuff that comes straight from the user, when it could be done like I did it which gives plenty of opportunity for validation too.
Copy linkTweet thisAlerts:
@jamesbcox1980Jul 21.2011 — He never said it was coming from the user, he just said "they stored it in a string". Anyway, what's wrong with using eval()?
Copy linkTweet thisAlerts:
@rnd_meJul 21.2011 — i cannot imagine any problem using eval() for client-side stuff.

after all, firebug's console is one big eval, and the world still turns...

i think that the evaluation of json paths is one of the best possible uses of eval...
Copy linkTweet thisAlerts:
@rnd_meJul 21.2011 — [CODE]var x = {y:{z:10}};

var a = "y.z";

a.split(".").map(function(a){
return this[0]=this[0][a];
}, [x]).slice(-1)[0];


[/CODE]
Copy linkTweet thisAlerts:
@jeanlucaauthorJul 22.2011 — [CODE]var x = {y:{z:10}};

var a = "y.z";

a.split(".").map(function(a){
return this[0]=this[0][a];
}, [x]).slice(-1)[0];
[/CODE]
[/QUOTE]

awesome split/map/slice example, but absolutely not readable ?
Copy linkTweet thisAlerts:
@JMRKERJul 22.2011 — [CODE]var x = {y:{z:10}};

var a = "y.z";

a.split(".").map(function(a){
return this[0]=this[0][a];
}, [x]).slice(-1)[0];


[/CODE]
[/QUOTE]

?

So how does it work? How is it called or used?

I tried and alert and assignment but see no results.

Do you have an example where it would be useful?
Copy linkTweet thisAlerts:
@jeanlucaauthorJul 23.2011 — If you want to see something do

[CODE]
var x = {y:{z:10}};

var a = "y.z";

console.log( a.split(".")
.map(function(a)
{
console.dir(this) ;
return this[0]=this[0][a];
}, [x])
.slice(-1)[0]) ;
[/CODE]

You can also remove the slice part and change the console.log into console.dir. This will get you started!

cheers
Copy linkTweet thisAlerts:
@rnd_meJul 23.2011 — :confused:
So how does it work? How is it called or used?

I tried and alert and assignment but see no results.

Do you have an example where it would be useful?[/QUOTE]


i forget not everyone use map(). i don't know why they don't; it makes js so much easier, but alas, they don't...

so, let's walk through the code and inspect what's going on here.
[CODE]var x = {y:{z:10}};
var a = "y.z";

a.split(".").map(function(a){
return this[0]=this[0][a];
}, [x]).slice(-1)[0];[/CODE]



these are obvious and drawn from previous examples. a deep object and string object path.
[CODE]var x = {y:{z:10}};
var a = "y.z";
[/CODE]



let's turn the string path into an array where each element is a deeper sub-branch name of our deep object:
[CODE]a.split(".")[/CODE]


now, we know that map() collects things from arrays. so, we take our path array and do something to each element, which is a path.

Now, just because we start out with a path string, doesn't mean that's what we have to return from a map function.

you can return anything, and (without getting ahead) we return sub-objects.


also consider that map accepts two arguments: (fn, that). the first is a function that will be called once for each element in our path array.

this function's first argument will be the element's value: a string path branch. other arguments are not used.

the 2nd arg to [].map specifies what the keyword "this" is to mean when the function executes:
[CODE].map( fn, that)[/CODE]

in the code, we set "this" to be a new array, containing the deep object from the first line of code. [CODE]}, [x])[/CODE]This part is admittedly the most confusing aspect.

why an array? because we don't have a named handle to x, so we create a new container for it. we could simply use the term "x" instead of "this" inside our map function, but that could re-define "x", breaking any outside code that used "x" as expected outside the map.

the other factor is that we need an object that is the same inside every map function call. since the map function's arguments change with every execution, we need to use "this" to provide stability; a permanent and re-usable object to modify and persist the changes. Because javascript is protective, we cannot simply re-assign "this" ala "this=this.y", because re-assigning "this" throws an error. we can re-assign this[0] however, and that's the magic that enables us to use a purer function instead of a closure-reliant function that could modify variables outside the map function. if we modify this[0], the next time the filter function fires, this[0] will be whatever we last set it to. think of it as a persistent namespace using numbers instead of names.

we need reachable and changeable access to x: [x][0]==x, so [x] is useful as such.

so, let's review.

we have a function that will be passed the string path name branch from our array of branches, and inside it will live an array named "this" that contains the object (x) we are manipulating (drilling down).


the meat:
[CODE]return this[0]=this[0][a];[/CODE]

could be simpler written as (don't use, destructive):
[CODE]x=x[a];
return x;
[/CODE]


that could would turn the object "x" into a sub-object of x, the branch name being whatever the named argument "a" happens to represent. in out case, "a" represent a branch name: it's a single element from path branch array we created at the start.

as written, our code set's this[0] to this[0]'s sub branch, named by "a".

it then returns this new sub-object to the collector.


the collector? huh?

yes, .map() collects outputs, any output. what you put into .map() ain't always what comes out. in fact, it is hardly ever the same in and out...

after every execution of our map function, we return the new sub-object.

after the map funciton has fired on every element in the path array, we have an output array which is a collection of all the returns. In our case, the returns (and thus the elements of the collector array) are essentially an ever-deeper set of sub-objects of "x".

the last element of the new array is the deepest sub-branch of "x" that was names by our path array.


running a output.slice(-1) gives use the last and deepest sub-object as a one-element array (slice always returns an array).

that array at position 0 (the only slot) is the deepest sub object defined by a branch name from our path string; the right-most segment.


thus we have a sub-object defined by the path, pulled from "x", without any intermediate variables or loop code.


i realize this is over-kill depth-wise, but i wanted to make sure i didn't leave out any details that might be needed.

let me know if any part is unclear, and i'll elaborate.
Copy linkTweet thisAlerts:
@JMRKERJul 23.2011 — i forget not everyone use map(). i don't know why they don't; it makes js so much easier, but alas, they don't...

so, let's walk through the code and inspect what's going on here.

.....

i realize this is over-kill depth-wise, but i wanted to make sure i didn't leave out any details that might be needed.

let me know if any part is unclear, and i'll elaborate.[/QUOTE]


That's quite an elaborate explanation and much appreciated for my current level of understanding. ?
Copy linkTweet thisAlerts:
@astupidnameJul 25.2011 — i forget not everyone use map(). i don't know why they don't; it makes js so much easier, but alas, they don't...[/quote]
Allow me to play devils advocate here a moment... ?

1. Array.prototype.map is not backwards comatible with all browsers, you totally left out the part about having to actually include the Array.prototype.map function definition in your scripts to allow it's use in browsers not supporting .map

e.g.
if (!Array.prototype.map)
{
Array.prototype.map = function(fun /*, thisp */)
{
"use strict";
if (this === void 0 || this === null){
throw new TypeError();
}
var t = Object(this);
var len = t.length &gt;&gt;&gt; 0;
if (typeof fun !== "function"){
throw new TypeError();
}
var res = new Array(len);
var thisp = arguments[1];
for (var i = 0; i &lt; len; i++)
{
if (i in t){
res[i] = fun.call(thisp, t[i], i, t);
}
}
return res;
};
}

2. .map actually in just about all cases results in code bloat (we already lost over 480 characters above, even compressed that comes down to 343. Not a terribly large amount, I know, but it all adds up) and also contributes to illegibility:
but absolutely not readable[/quote]
All .map is, is a container for looping which code-writers sometimes get perhaps bored of or wrongly think is less efficient than hiding the looping inside of a .map or other iterator function. The fact that you need define a function to pass to the .map function results in perhaps a bit more complexity than needed, when almost always (no, actually I mean absolutely always) a straight forward loop is more readable and less code overall required in the majority of cases.

Take for example a .map version of this project versus a simple loop:

var x = {y:{z:10}};
var a = "y.z";

var n = a.split(".").map(
function(a) {
return this[0] = this[0][a];
},
[x]
).slice(-1)[0];
//n defined in 106 chars
alert(n);

var i,
n = x,
aa = a.split('.');
for (i = 0; i &lt; aa.length; i++) {
n = n[aa[i]];
}
//n defined in 89 chars
alert(n);


Now comes the eye of the beholder part, I guess, but which of the above is more readable? Even compressed the second version wins in the compactness department:
var n=a.split(".").map(function(a){return this[0]=this[0][a];},[x]).slice(-1)[0];
alert(n);

var i,n=x,aa=a.split('.');for(i=0;i&lt;aa.length;i++){n=n[aa[i]];}
alert(n);


Don't get me wrong, me loves functions and anonymous functions, they have many excellent uses, but I personally don't find .map desirable for the above reasons. To me it only SEEMS like a cool trick to use .map
Copy linkTweet thisAlerts:
@rnd_meJul 25.2011 —  a straight forward loop is more readable and less code overall required in the majority of cases.

...

Even compressed the second version wins in the compactness department:

...

Don't get me wrong, me loves functions and anonymous functions, they have many excellent uses, but I personally don't find .map desirable for the above reasons. To me it only SEEMS like a cool trick to use .map[/QUOTE]


Array.map works in all current browsers. it's worked in most browsers for years.

on a standalone example, yes, hard-coded loops will likely be simpler and shorter, but on my production code over several projects, [].map has saved me a ton of coding time and fragility for the minor expense of a bootstrap to support ie8 and before. i guess i just think in [].map now; it's just how i approach set operations.


compression?

your compatibility replacement function is bulky and overly-cautious.

you don't need elaborate stack traces for ie7, so skip all the expensive frivolities, which also reduces code bloat; an important criterion for you.

~500 bytes for a single array proto? no wonder you don't like 1.6!

I can define all the 1.6 methods ("map", "filter", "every", "some", "lastIndexOf", "indexOf", "reduce", "reduceRight", "forEach", "clean" (a custom map/filter hybrid) ) in <900 bytes:

[CODE]eval(function (s,r){var a,x,i=0,m=r.length,q=/([.*+?^${}()|[]/\])/g;for(;i<m;i++){a=r[i]||"";x=RegExp(a.slice(0,1).replace(q,"\$1"),"g");s=s.replace(x,a.slice(1))}return s}("(&n(#var o=Array.prototype,H,i,e={map/i=0,r=[]BVr[i]8G,V}Nr9J<[email protected](G,V#r[g++Kt[i]MNr9every/i=0;[email protected]==m9some/i=1;Q(;mT;#[email protected](tDmCm,V@!Ti#$!0}}$!19lastIndexOf/i=b6-1;Q(;m>i;mT#P@t[mK==a#Nl}}$-194dexOf/i=b60Bt@t[iK==a#Ni}}$-19reduce/i=0,r=b6t[i++]L57r8nul*reduceRight/i=m-1,r=b6t[iT]&#37;>-1;iT#r8nul*QEach:&n(a,b#this.concat().mapU9clean<0,x,O;a=Function(a6Str4g)Bt@(x8G,V)#r[g++KxMNarguments[2]?b:rMQ(H 4 e#i=o[H];o[HKi6e[H]}}());","Vt)U(a,b)T--BT4 TLB Pif(m 4 tN$ M}};</i=0,r=[Cg=L%&lt;K]=JfilterHitGbDiCiD,t[C],6||B576Qfor&amp;functio@&&9},8=a.3(7++#6if(i5m;i4in3call#){/:#,*l,r,t[i],i,t)}$ r},%;for(;i$return#functio$length$n(a,b){var t=#=t.#this.concat(),m".split('')));[/CODE]

an example loop may seem simpler, but consider that any example loop is hard-coded with variable names and thus single-purpose, whereas you can use reusable "pure functions" to filter and transform any array.

in short, it's like using a variable for a state transforms, instead of a state-changing procedural delectation. in other words, it's akin to using Math.PI instead of always writing-out 3.14159278...

also consider that many transforms and filters are built in, so you don't need any loop code or special custom functions:
[CODE]
var r=[0,1, "2",3,"fred", false, "", NaN, "sally", undefined, "jon"];

r.filter(Boolean)// [1, "2", 3, "fred", "sally", "jon"]
r.map(Number).filter(Number)// [1, 2, 3]
r.map(Number).filter(isFinite)// [0, 1, 2, 3, 0, 0]
r.filter(isNaN).filter(Boolean).map(String)//["fred", "sally", "jon"]
[/CODE]


Notice how Number can act as a filter or a map?

-when's the last time a loop can do two things with the exact same code called differently? oh wait, you can't call a loop, so you're stuck with one set of functionality, or a performance and maintenance crippling IF statement...


functional programming also has the advantage of offering naive opt-in closure where commonly expected:

[CODE](Array(4)+0).split(",").map(function(a,i){

setTimeout(function(){ alert(i);}, i * 2000 )

})[/CODE]

-vs-

[CODE]for(var i=0;i<4;i++){
setTimeout(function(){ alert(i);}, i * 2000 )
}[/CODE]



consider [].map's multi-threading advantages as well:

i can't easily break up a loops workload, but i can easily slice an array in half and dispatch it to two separate worker threads using the same pure function without recoding the data or state-change-operation (function/loop body).


lastly, i don't think code-size or readability are the end-all be all criteria for judging a patterns appropriateness for a given problem.

frankly a lot of people doing js ceom from other languages, so the most readable would be the most C-like. but that's not the bext way to do thing in JS.

the now-standard prototype methods are the best thing to happen to javascript this century.


i find that with a small collection of pure functions, i can save a ton of loop code in nearly all my apps. i highly recommend the pattern for all.
Copy linkTweet thisAlerts:
@jeanlucaauthorJul 25.2011 — Very interesting examples. I noticed the following line in the map function which I don't understand:

[CODE]
var len = t.length >>> 0;
[/CODE]


why not just [B]var len = t.length[/B]. The result is the same!
×

Success!

Help @jeanluca spread the word by sharing this article on Twitter...

Tweet This
Sign in
Forgot password?
Sign in with TwitchSign in with GithubCreate Account
about: ({
version: 0.1.9 BETA 5.20,
whats_new: community page,
up_next: more Davinci•003 tasks,
coming_soon: events calendar,
social: @webDeveloperHQ
});

legal: ({
terms: of use,
privacy: policy
});
changelog: (
version: 0.1.9,
notes: added community page

version: 0.1.8,
notes: added Davinci•003

version: 0.1.7,
notes: upvote answers to bounties

version: 0.1.6,
notes: article editor refresh
)...
recent_tips: (
tipper: @AriseFacilitySolutions09,
tipped: article
amount: 1000 SATS,

tipper: @Yussuf4331,
tipped: article
amount: 1000 SATS,

tipper: @darkwebsites540,
tipped: article
amount: 10 SATS,
)...