3

Hi everyone I need to write a function that takes a string and object and interpolates that object in the string so something like this

// interpolate("Its {weather} outside", {weather: 'damn Hot'})
// returns 'It's damn hot outside'


// interpolate( "Hi my name is {name}", {name: 'John'});
// returns 'Hi my name is John'

It should also no matter how deep the object goes so a case like this

// interpolate("Hi my name is {contact.name}", {contact: {name: 'john'}});

Im a little stuck and at first I tried splitting up the string into an array then trying to fit the object value in the array then join that back together but that has not worked for all test cases, can someone help me write this function and explain their solution please? Thankyou

so I tried something like this but does not really work for all test cases, this is a template literal but the function should just work giving those values as arguments on its own, otherwise I'm pretty stuck . .

function interpolate(strings, ...keys) {
  return (function(...values) {
    var dict = values[values.length - 1] || {};
    var result = [strings[0]];
    keys.forEach(function(key, i) {
      var value = Number.isInteger(key) ? values[key] : dict[key];
      result.push(value, strings[i + 1]);
    });
    return result.join('');
  });
}

var t1Closure = interpolate`${0}${1}${0}!`;
t1Closure('Y', 'A');  // "YAY!" 
var t2Closure = interpolate`${0} ${'foo'}!`;
console.log(t2Closure('Hello', {foo: 'World'}));  // "Hello World!"

ok I'm getting closer so I separated the problem into two functions and need to combine them, the only thing is I'm not sure how to get the last use case to work without template literals

var something = "something";
var sub = function(str) {
   return str.replace(/#\{(.*?)\}/g,
    function(whole, expr) {
      return eval(expr)
    })
}

console.log(sub("hello #{something}"));



var objectValue = function(str, obj){

     for(key in obj) {
       if(obj.hasOwnProperty(key)) {
          var value = obj[key];
          return str + value;
      }
   }
}


console.log(objectValue("hi my name is ", {contact: {name: 'john'}}));
Jack
  • 33
  • 4
  • 1
    [What have you tried so far that isn't working?](http://whathaveyoutried.com). – ManoDestra Jun 06 '16 at 20:04
  • Show us the problematic code you've written so far. – Mark Bolusmjak Jun 06 '16 at 20:06
  • Have you considered using ES6 template strings? – gcampbell Jun 06 '16 at 20:06
  • 1
    I'm sorry you did not succeed, but nobody is going to write the code for you. Post whatever you tried, explain the place you failed and community will be glad to help you. – Zabavsky Jun 06 '16 at 20:07
  • Can you have multiple object (keys) parameters? This would present a problem that would need to be addressed if both objects had the same property name. – Zach Jun 06 '16 at 20:18
  • 1
    You should have a look at [How can I construct a Template String from a regular string?](http://stackoverflow.com/q/29771597/1048572) and possibly [Defer execution for ES6 Template Strings](http://stackoverflow.com/q/22607806/1048572). In summary, template literals are not the best tool for this. Use a templating library instead. – Bergi Jun 06 '16 at 20:36

5 Answers5

3

Using the dreaded eval

If you control the string that you pass and consider it safe, you can use eval:

function interpolate (str, obj)  {
    return str.replace(/\{(.*?)\}/g, function (_, ref) {
        return eval('obj.' + ref);
    });
}

var output = interpolate("Its {weather} outside", {weather: 'damn Hot'});
console.log(output);

output = interpolate("Hi my name is {contact.name}", {contact: {name: 'john'}});
console.log(output);

output = interpolate("The highest bid is {rank[0].bid}", {rank: [{bid: 900}, {bid:600}]});
console.log(output);

Be aware that if given a string like '{x;alert("hi")}', the above function would execute that alert, or any code that is put instead. So this is not a good solution if the string is provided (or can be altered) by the user.

Template Literals

It does not follow your function descriptor, but template literals already offer the functionality you are looking for:

var weather = 'damn Hot';
var output = `It's ${weather} outside`;
console.log(output);

var contact = {name: 'john'};
var output = `Hi my name is ${contact.name}`;
console.log(output);
trincot
  • 317,000
  • 35
  • 244
  • 286
1

Here you go:

'use strict';
function interpolate(str, obj) {
  for (let prop in obj) {
    if (obj.hasOwnProperty(prop)) {
        str = str.replace(new RegExp(`{${prop}}`, 'g'), obj[prop]);
    }
  }
  return str;
}
console.log(interpolate("Its {weather} outside", {weather: 'damn Hot'}));
// Its damn Hot outside
Relu Mesaros
  • 4,952
  • 4
  • 25
  • 33
0

Try renderjs.

$("<p>Its {{:weather}} outside</p>").render({weather: 'damn Hot'}) 
-> <p>Its damn Hot outside</p>
Grim
  • 1,938
  • 10
  • 56
  • 123
0

Or lodash templates. Lodash already has a lot of handy features you end up using a lot of in my opinion.

var str = _.template('Its <%= weather %> outside')({weather: 'damn Hot'});

// If you wanted to use the {{:var}} syntax.
_.templateSettings.interpolate = /{{:([\s\S]+?)}}/g;
var str = _.template('Its {{:weather}} outside')({weather: 'damn Hot'});
micah
  • 7,596
  • 10
  • 49
  • 90
0

A little late to the party, but in case you cannot (don't want to) use any external libraries or ES6, here is the idea:

function getMatches(s) {
    var regExp = /{([^}]*)}/g,
        matches = [],
        match;

    while ((match = regExp.exec(s)) != null) {
        matches.push(match[1]);
    }
    return matches;
}

function objValue(obj, i) {
    return obj[i];
}

function interpolate(s, obj) {
    var matches = getMatches(s),
        result = s;

    matches.forEach(function (match) {
        var props = match.split('.'),
            value = props.reduce(objValue, obj) || '';

        result = result.replace('{' + match + '}', value);
    });
    return result;
}

Usage

interpolate("Its {weather} outside", { weather: 'damn Hot' });

JSFiddle.

Zabavsky
  • 13,340
  • 8
  • 54
  • 79