public
Last active

Dead simple straight-up performant interpolation

  • Download Gist
makeInterpolator.js
JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
/**
* Outputs a new function with interpolated object property values.
* Use like so:
* var fn = makeInterpolator('some/url/{param1}/{param2}');
* fn({ param1: 123, param2: 456 }); // => 'some/url/123/456'
*/
var makeInterpolator = (function() {
var rc = {
'\n': '\\n', '\"': '\\\"',
'\u2028': '\\u2028', '\u2029': '\\u2029'
};
return function makeInterpolator(str) {
return new Function(
'o',
'return "' + (
str
.replace(/["\n\r\u2028\u2029]/g, function($0) {
return rc[$0];
})
.replace(/\{([\s\S]+?)\}/g, '" + o["$1"] + "')
) + '";'
);
};
}());
 
var url = makeInterpolator('http://something.com/{param}/{foo}?x={n}');
 
url({
param: '123',
foo: '456',
n: 789
});

Blog post with explanation: http://james.padolsey.com/javascript/straight-up-interpolation/


Really clever code.

P.S. Nice use of URI templates syntax.
P.P.S. I use $0, $1, … as identifiers for arguments to String#replace callbacks, too! :)

Why not do the first 3 replace calls with a single one instead? (Or am I missing something?)

leaflet has a more compact version that turns it into one regex

function leafletInterpolater(str, data) {
    return str.replace(/\{ *([\w_]+) *\}/g, function (str, key) {
        var value = data[key];
        if (value === undefined) {
            throw new Error('No value provided for variable ' + str);
        } else if (typeof value === 'function') {
            value = value(data);
        }
        return value;
    });
}

in this case you'd either do

leafletInterpolater('http://something.com/{param}/{foo}?x={n}',{
  param: '123',
  foo: '456',
  n: 789
})

or

var url = leafletInterpolater.bind(undefined,'http://something.com/{param}/{foo}?x={n}');
url({
  param: '123',
  foo: '456',
  n: 789
});
.replace(/\{([\s\S]+?)\}/g, function($0, $1) {
        return '" + o["' + $1 + '"] + "';
      })

// =>

.replace(/\{([\s\S]+?)\}/g, '" + o["$1"] + "')

Unless I'm missing something? (In other words, you can use $1 inside the string for replace to do the same thing, young padolseywan.)

Also, why .replace(/"/g, '\\$&') and not just .replace(/"/g, '\\"') ? Easier to read.

@qfox -- ah please excuse the verbose code -- tis remnant of legacy iterations. I shall correct it :)

@JamesMGreene: I tried something like this:

replace(/["\r\n]/g, '\\$&')

But since we would then be replacing a literal \n with \\\n it won't work. The compiled Function requires the actual literal string \\n but this cannot be generated in such a meta way without directly writing it AFAIK. (brain silently implodes)

Give it a try though -- I may have missed something very obvious...

@calvinmetcalf AFAICT that runs the replace(...) at runtime when passing the data, right? My intent with this interpolator was runtime performance, sacrificing creation-time performance, hence the creation of a compiled Function with a straight-up concatenation inside.

@mathiasbynens: tbh I wasn't inspired by RFC6570 -- but looking at it now it seems v. useful. I also found https://github.com/fxa/uritemplate-js

@padolsey FWIW, @rodneyrehm’s URI.js also supports it: http://medialize.github.io/URI.js/uri-template.html

Only special-casing \n and \r is not enough. Having \u2028 or \u2029 in the input string still throws an error. Just replace them with their (double-)escaped equivalents and you’ll be fine.

@mathiasbynens: cool, thanks! -- just edited it.

@padolsey I got what you were going for after I read your blog post

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.