0

Looked around SO and didn't find anything that seemed to match what I am trying to do..

I am trying to reference an object by a string representation, though everywhere I look I see that using eval() is bad - though can't find a way to do this without using eval()

So my use case:

I have a data attribute on a button;

data-original-data-object="window.app.myData.originalData"

When the button is clicked I need to access the actual object held at window.app.myData.originalData

Now, I know I can do:

var dataObj = eval($(this).data('original-data-object'));

Though is there any other way to do this?

If it helps, the data that is stored at window.app.myData.originalData is a JSON object.

Darren Wainwright
  • 30,247
  • 21
  • 76
  • 127
  • This is one of the few areas where eval is acceptable imo. The alternative is to use an [lookup function](https://gist.github.com/megawac/6162481#file-underscore-lookup-js) – megawac Jan 29 '14 at 18:41
  • Check my answer to [is it evil to use eval to convert a string to a function?](http://stackoverflow.com/questions/14396647/is-it-evil-to-use-eval-to-convert-a-string-to-a-function) – Bergi Jan 29 '14 at 18:43

4 Answers4

3

Like this:

var obj = (function(str){
  var arr = str.split('.');

  if (arr[0] === 'window'){
    arr.shift();
  }

  return arr.reduce(function(a, b){
     return a[b];
  }, window);

}("window.app.myData.originalData"))
CD..
  • 72,281
  • 25
  • 154
  • 163
1

A couple of solutions come to mind. The first solution is hinted at in @CD..'s answer. The second is to restrict that string via a regex to just property names so you can safely use eval.

Traversing the window object to get the value (no eval)

function getValue(s) {
    var keys = s.split("."), o = window, key, i, length, undef;

    if (keys[0] === "window") {
        keys.shift();
    }

    for (i = 0, length = keys.length; i < length; i++) {
        key = keys[i];

        if (!(key in o) || o[key] === null || o[key] === undef) {
            throw new Error("Could not get value of " + s);
        }

        o = o[key];
    }

    return o;
}

Restricting the string to valid property names:

function getValue(s) {
    var regex = /^[\w$][\w.]+$/, value;

    if (regex.test(s)) {
        try {
            value = eval(s);
        }
        catch (error) {
            throw new Error("Could not get value of " + s + " (" + error.message + ")");
        }
    }
    else {
        throw new Error("Could not get value of " + s);
    }

    return value;
}

To use:

var x = getValue(this.getAttribute("data-original-data-object"));

You want to avoid using eval because it can arbitrarily execute JavaScript that you may or may not have control of. In this particular case, you know the exact kind of string you want. In my opinion, I'd use a regular expression to make sure the string just contains property names separated by dots. Security speaking, there is no difference between these two lines of code:

var x = eval("window.foo");
var x = window.foo;
Greg Burghardt
  • 17,900
  • 9
  • 49
  • 92
  • I like the second one - though you've missed out the `return value;` part. how does this compare in terms of performance with the first one, and also `eval()` - any idea? – Darren Wainwright Jan 29 '14 at 18:54
  • You want a return value? That'll cost ya extra! :) Updated my answer. I'm not sure what the performance differences are. Unless you want the value of `window.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r` I'm sure the performance is nearly the same. It's hard to benchmark something like this in JavaScript. – Greg Burghardt Jan 29 '14 at 18:56
0

Provided that you can ensure that the attribute cannot be modified in anyway that can cause harm to the site/project that this is being implemented on, I don't see any problems.

Sharikul Islam
  • 319
  • 2
  • 8
  • The downside is that the attribute is in HTML, so messing with the console could cause an issue - though in this case it's not a public site. – Darren Wainwright Jan 29 '14 at 18:44
  • @Darren: What do you mean? Messing the the console can always cause issues. – Bergi Jan 29 '14 at 18:44
  • ya i guess, though wasn't sure if I was opening up some possible issue of dodgy-code being added to the `data-original-data-object`. it's a highly unlikely scenario in this case. – Darren Wainwright Jan 29 '14 at 18:47
0

I'm not sure if this will work for your situation, but a simple solution that avoids eval may be to add "window.app.myData.originalData" with its JSON data as the property of an object that will remain in scope.

Something like:

var exampleData = { id:1, content:"..." };

var dataStore = { "window.app.myData.originalData": exampleData  };

Then, in your click handler:

var retrievedData = dataStore[$(this).data('original-data-object')];    // uses "window.app.myData.originalData" to retrieve exampleData

In this case, you will need to access the data using bracket notation because of the . character in the property name. This approach should be faster and safer than trying to use eval, however.

polyslush
  • 111
  • 3
  • That would work, though in this case it means more variables to store a reference to another object - little too much overhead for me, as the app grows, I think. Then if the object `window.app.myData.originalData` undergoes a name-change, then its more places to change it. Thanks though :) – Darren Wainwright Jan 29 '14 at 19:35
  • You could probably just dynamically generate that dataStore object upfront using jQuery to retrieve data attributes for the property names - it would mitigate the need to maintain the object's properties, and name changes wouldn't pose an issue. Without knowing your exact requirements, however, I admit that this approach may not be ideal for you. – polyslush Jan 29 '14 at 19:56