Last active

Embed URL

HTTPS clone URL

SSH clone URL

You can clone with HTTPS or SSH.

Download Gist

Dead simple straight-up performant interpolation

View makeInterpolator.js
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
});
qfox commented
.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.)

qfox commented

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

Owner

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

Owner

@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...

Owner

@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.

Owner

@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.

Owner

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

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.