I did this. But is it a good approach ?
I can not really tell, but ... Consider this ...
var format = (str, obj) => {
str = str.replaceAll('{', '${obj.');
console.log(str);
let newStr = eval('`' + str + '`');
console.log(newStr);
}
format('{alert("Think before \\"eval\\"uating!")}', window);
.as-console-wrapper { min-height: 100%!important; top: 0; }
Read on SO about why eval has limited use cases ... why is eval evil
Edit
For a custom template-string evaluation / interpolation I would stick to the syntax of JavaScript Template Literals of cause without enclosing the template string by backticks.
For the substitution one needs a regex which targets both the valid substitute template syntax ${ foo.bar }
and a valid object path syntax. Thus variable/property names can start with $
, _
and upper/lowercase latin letters only, whereas within a variable name the numbers from 0
to 9
are allowed. Thus a regex which does capture such a valid object path from a valid template syntax looks like this ...
/\$\{\s*(?<path>[a-zA-Z_$]\w*(?:\.[a-zA-Z_$]\w*)*)\s*\}/g
Value interpolation then is not that complicated anymore. One just does split
an object path like 'foo.bar.baz'
into an array of property names like ['foo', 'bar', 'baz']
. The final value can be evaluated via a simple reduce
task which programmatically walks down the property chain of the provided object/type ...
function evaluateCustomTemplate(template, type) {
function evaluateValue(value, key) {
return value && value[key];
}
function interpolateValue(match, path) {
return path
.split(/\./)
.reduce(evaluateValue, type);
}
const regXValidObjectPath =
// [https://regex101.com/r/OtNiAB/1/]
(/\$\{\s*(?<path>[a-zA-Z_$]\w*(?:\.[a-zA-Z_$]\w*)*)\s*\}/g);
return template.replace(regXValidObjectPath, interpolateValue);
}
console.log(
evaluateCustomTemplate(
'Hi, ${ alert("Think before \\"eval\\"uating!") }!',
window
)
);
console.log(
evaluateCustomTemplate(
'Hello, ${ name }!',
{ name: 'world' }
)
);
console.log(
evaluateCustomTemplate(
'Hello, ${ user.name.first } ${ user.name.last }!',
{ user: { name: { first: 'John', last: 'Smith' } } }
)
);
console.log(
evaluateCustomTemplate(
'Hello, ${ user.name.first.f1 } ${ user.name.last }!',
{ user: { name: { first: { f1:'John' }, last: 'Smith' } } }
)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
The above approach based on a less rigid object-path regex like ... /\$\{([^}]+)\}/g
... then turns into the one beneath ...
function evaluateCustomTemplate(template, type) {
function interpolateValue(match, path) {
return path
.trim() // minimum whitespace sanitation.
.split(/\./)
.reduce((value, key) => value && value[key], type);
}
// [https://regex101.com/r/OtNiAB/2/]
const regXDirtyObjectPath = (/\$\{([^}]+)\}/g);
return template.replace(regXDirtyObjectPath, interpolateValue);
}
console.log(
evaluateCustomTemplate(
'Hi, ${ alert("Think before \\"eval\\"uating!") }!',
window
)
);
console.log(
evaluateCustomTemplate(
'Hello, ${ name }!',
{ name: 'world' }
)
);
console.log(
evaluateCustomTemplate(
'Hello, ${ user.name.first } ${ user.name.last }!',
{ user: { name: { first: 'John', last: 'Smith' } } }
)
);
console.log(
evaluateCustomTemplate(
'Hello, ${ user.name.first.f1 } ${ user.name.last }!',
{ user: { name: { first: { f1:'John' }, last: 'Smith' } } }
)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }