views:

100

answers:

3

Consider this javascript:

var values = {
    name: "Joe Smith",
    location: {
        city: "Los Angeles",
        state: "California"
    }
}

var string = "{name} is currently in {location.city}, {location.state}";

var out = string.replace(/{([\w\.]+)}/g, function(wholematch,firstmatch) {
    return typeof values[firstmatch] !== 'undefined' ? 
        values[firstmatch] : wholematch;
});

This will output the following:

Joe Smith is currently in {location.city}, {location.state}

But I want to output the following:

Joe Smith is currently in Los Angeles, California

I'm looking for a good way to convert multiple levels of dot notation found between braces in the string into multiple parameters to be used with bracket notation, like this:

values[first][second][third][etc]

Essentially, for this example, I'm trying to figure out what regex string and function I would need to end up with the equivalent of:

out = values[name] + " is currently in " + values["location"]["city"] +
    values["location"]["state"];

NOTE: I'd like to do this without using eval().

+5  A: 

Using a helper function to iteratively access the properties:

function getNestedValue(obj, prop) {
  var value, props = prop.split('.'); // split property names

  for (var i = 0; i < props.length; i++) {
    if (typeof obj != "undefined") {
      obj = obj[props[i]]; // go next level
    }
  }
  return obj;
}

var string = "{name} is currently in {location.city}, {location.state}";

var out = string.replace(/{([^}]+)}/g, function(wholematch,firstmatch) {
  var value = getNestedValue(joe, firstmatch);
  return typeof value !== 'undefined' ? value : wholematch;
});
// "Joe Smith is currently in Los Angeles, California"

Try the above example here.

Edit: Something slightly elegant, using the Array.prototype.reduce method, part of the new ECMAScript 5th Edition Standard:

function replace(str, obj) {
  return str.replace(/{([^}]+)}/g, function(wholematch,firstmatch) {
    var value = firstmatch.split('.').reduce(function (a, b) {
      return a[b];
    }, obj);
    return typeof value !== 'undefined' ? value : wholematch;
  });
}

replace("{name} is currently in {location.city}, {location.state}", values);
// "Joe Smith is currently in Los Angeles, California"

Try the new example here.

CMS
@CMS: nice, that works! However, it doesn't seem as elegant as a regex-based solution, and I'd like to believe there is a regex way to do it. Then again, maybe it would be a waste of time to pursue a regex solution.
Tauren
@CMS: doesn't your regex allow anything between the braces? if i want to limit values to valid javascript variable names `[a-zA-Z0-9_\.]`, wouldn't it be best to use `/{([\w\.]+)}/g` ? Actually, that would allow a period at the beginning, which isn't allowed, but it's close. Oh yeah, I think I need to include `$` as well.
Tauren
@Tauren, right it's a bit permissive, I didn't care too much, because when you access properties with the bracket notation (or use strings in an object literal), there are no restrictions, i.e. `obj["3.xx\u0009"]` ...
CMS
@Tauren, give a look to my edit...
CMS
@CMS: very cool! too bad `reduce` isn't supported universally yet, but I guess `Array.prototype.reduce` isn't that big to include.
Tauren
@Tauren, It isn't big, a standard compliant version is available [here](https://developer.mozilla.org/En/Core_JavaScript_1.5_Reference/Global_Objects/Array/Reduce#Compatibility)... IE is the only major browser that lacks of this standard methods...
CMS
A: 

I'm not sure how to integrate this in Javascript, but here's a Java snippet that uses regex to do the transformation you need:

    String[] tests = {
        "{name}",
        "{location.city}",
        "{location.state.zip.bleh}",
    };
    for (String test : tests) {
        System.out.println(test.replaceAll("\\{?(\\w+).?", "[$1]"));
    }

This prints:

[name]
[location][city]
[location][state][zip][bleh]

Essentially it replaces all occurrences of \{?(\w+).? to [$1].

polygenelubricants
hmm, interesting. However, even if I got this working in JS, I'm thinking I would end up with a string that I'd need to `eval`, which I'd like to avoid.
Tauren
A: 

You can take a look at the topic Error-free deep property access? posted at comp.lang.javascript - this presents several approaches for doing what you want.

For the regexp, all you need is /{.*?}/g

Sean Kinsey