Format a JavaScript string using placeholders and an object of substitutions?
Solution 1
The requirements of the original question clearly couldn't benefit from string interpolation, as it seems like it's a runtime processing of arbitrary replacement keys.
However, if you just had to do string interpolation, you can use:
const str = `My name is ${replacements.name} and my age is ${replacements.age}.`
Note the backticks delimiting the string, they are required.
For an answer suiting the particular OP's requirement, you could use String.prototype.replace()
for the replacements.
The following code will handle all matches and not touch ones without a replacement (so long as your replacement values are all strings, if not, see below).
var replacements = {"%NAME%":"Mike","%AGE%":"26","%EVENT%":"20"},
str = 'My Name is %NAME% and my age is %AGE%.';
str = str.replace(/%\w+%/g, function(all) {
return replacements[all] || all;
});
If some of your replacements are not strings, be sure they exists in the object first. If you have a format like the example, i.e. wrapped in percentage signs, you can use the in
operator to achieve this.
However, if your format doesn't have a special format, i.e. any string, and your replacements object doesn't have a null
prototype, use Object.prototype.hasOwnProperty()
, unless you can guarantee that none of your potential replaced substrings will clash with property names on the prototype.
Otherwise, if your replacement string was 'hasOwnProperty'
, you would get a resultant messed up string.
As a side note, you should be called replacements
an Object
, not an Array
.
Solution 2
How about using ES6 template literals?
var a = "cat";
var b = "fat";
console.log(`my ${a} is ${b}`); //notice back-ticked string
More about template literals...
Solution 3
Currently there is still no native solution in Javascript for this behavior. Tagged templates are something related, but don't solve it.
Here there is a refactor of alex's solution with an object for replacements.
The solution uses arrow functions and a similar syntax for the placeholders as the native Javascript interpolation in template literals ({}
instead of %%
). Also there is no need to include delimiters (%
) in the names of the replacements.
There are two flavors (three with the update): descriptive, reduced, elegant reduced with groups.
Descriptive solution:
const stringWithPlaceholders = 'My Name is {name} and my age is {age}.';
const replacements = {
name: 'Mike',
age: '26',
};
const string = stringWithPlaceholders.replace(
/{\w+}/g,
placeholderWithDelimiters => {
const placeholderWithoutDelimiters = placeholderWithDelimiters.substring(
1,
placeholderWithDelimiters.length - 1,
);
const stringReplacement = replacements[placeholderWithoutDelimiters] || placeholderWithDelimiters;
return stringReplacement;
},
);
console.log(string);
Reduced solution:
const stringWithPlaceholders = 'My Name is {name} and my age is {age}.';
const replacements = {
name: 'Mike',
age: '26',
};
const string = stringWithPlaceholders.replace(/{\w+}/g, placeholder =>
replacements[placeholder.substring(1, placeholder.length - 1)] || placeholder
);
console.log(string);
UPDATE 2020-12-10
Elegant reduced solution with groups, as suggested by @Kade in the comments:
const stringWithPlaceholders = 'My Name is {name} and my age is {age}.';
const replacements = {
name: 'Mike',
age: '26',
};
const string = stringWithPlaceholders.replace(
/{(\w+)}/g,
(placeholderWithDelimiters, placeholderWithoutDelimiters) =>
replacements[placeholderWithoutDelimiters] || placeholderWithDelimiters
);
console.log(string);
UPDATE 2021-01-21
Support empty string as a replacement, as suggested by @Jesper in the comments:
const stringWithPlaceholders = 'My Name is {name} and my age is {age}.';
const replacements = {
name: 'Mike',
age: '',
};
const string = stringWithPlaceholders.replace(
/{(\w+)}/g,
(placeholderWithDelimiters, placeholderWithoutDelimiters) =>
replacements.hasOwnProperty(placeholderWithoutDelimiters) ?
replacements[placeholderWithoutDelimiters] : placeholderWithDelimiters
);
console.log(string);
Solution 4
You can use JQuery(jquery.validate.js) to make it work easily.
$.validator.format("My name is {0}, I'm {1} years old",["Bob","23"]);
Or if you want to use just that feature you can define that function and just use it like
function format(source, params) {
$.each(params,function (i, n) {
source = source.replace(new RegExp("\\{" + i + "\\}", "g"), n);
})
return source;
}
alert(format("{0} is a {1}", ["Michael", "Guy"]));
credit to jquery.validate.js team
Solution 5
As with modern browser, placeholder is supported by new version of Chrome / Firefox, similar as the C style function printf()
.
Placeholders:
%s
String.%d
,%i
Integer number.%f
Floating point number.%o
Object hyperlink.
e.g.
console.log("generation 0:\t%f, %f, %f", a1a1, a1a2, a2a2);
BTW, to see the output:
- In Chrome, use shortcut
Ctrl + Shift + J
orF12
to open developer tool. - In Firefox, use shortcut
Ctrl + Shift + K
orF12
to open developer tool.
@Update - nodejs support
Seems nodejs don't support %f
, instead, could use %d
in nodejs.
With %d
number will be printed as floating number, not just integer.
Related videos on Youtube
Comments
-
Joby Joseph about 2 years
I have a string with say:
My Name is %NAME% and my age is %AGE%.
%XXX%
are placeholders. We need to substitute values there from an object.Object looks like:
{"%NAME%":"Mike","%AGE%":"26","%EVENT%":"20"}
I need to parse the object and replace the string with corresponding values. So that final output will be:
My Name is Mike and my age is 26.
The whole thing has to be done either using pure javascript or jquery.
-
Joel Coehoorn over 12 yearsThat looks more like an object than an array
-
nnnnnn over 12 yearsWhat have you tried so far? Have you looked at the string .replace() method? (Also, you don't have an array there, you have an object.)
-
davidchambers over 12 yearsThat's pretty ugly. Surely you'd be just as well served by
{NAME: "Mike", AGE: 26, EVENT: 20}
? You'd still require that these keys appear bookended by percent signs in the input string, of course.
-
-
mu is too short over 12 yearsobligatory "please use MDN as a reference": developer.mozilla.org/en/JavaScript/Reference/Global_Objects/…
-
nnnnnn over 12 years+1. Nice. Though you might want to say
return replacements[all] || all
to cover%NotInReplacementsList%
cases. -
hafichuk over 12 yearsThanks for the spanking guys - appreciated ;)
-
RobG over 12 yearsCool, Alex did pretty much exactly the same thing but in fewer lines of code (though ternary operators are likely slower than if..else).
-
RobG over 12 yearsHey, I gave you a +1! You both did a replace function, yours isn't exactly the same but pretty similar. Your RegExp is different too, the OP would be better off using %% or $$ or similar as delimiters - a single % or % is likely to occur in a string normally, but doubles are unlikely.
-
Michael Härtl about 9 yearsThis will not work if the replacement value is falseish. So it's better to use this return statement:
return all in params ? params[all] : all;
-
user3167101 about 9 years@MichaelHärtl Shouldn't your replacements all be strings? If you want to replace with empty string, better off checking with other means.
-
Michael Härtl about 9 years@alex I had the situation where replacements could also be integers, and even
0
. In this case it didn't work. -
nabrown over 8 yearsYou definitely wouldn't want to load this plugin just for this, but I'm already using this to validate a form on the page...so thanks for the tip!
-
user3167101 over 8 years@MichaelHärtl Updated to cover that case.
-
user3167101 over 8 yearsPretty inefficient to create a regex for each number, it would be better to match all numbers and then replace on if the value was found in the array, perhaps?
-
user3167101 over 8 yearsThat's great, unless you want to do something besides log it to the console.
-
Raphael C over 7 yearsfor those interested, it happens here: github.com/jquery-validation/jquery-validation/blob/master/src/…
-
Hyyan Abo Fakher about 7 yearsAwesome, simple solution, I use it with different pattern /[[\w+]]/g
-
IonicBurger over 6 yearsBut since you can do it in es6 that is probably a bit overkill?
-
user3167101 over 6 yearsIf you have an object with placeholders like the OP, how does string interpolation help that?
-
Larphoid over 5 years+ very nice ... and for
$.each
you could makeString.prototype.format=function(p){var s=this,r=function(v,i){s=s.replace(new RegExp("\\{"+i+"\\}","g"),v);};p.forEach(r);return s;}
so you don't have to include jquery just for that one ;) -
BAERUS over 5 yearsWorks like a charm! Most pragmatic solution to my placeholding problems, perfect.
-
baku almost 5 yearsi made a variation on your function without prototypes
formatMessage(message: string, values: string[]) { let i = 0; return message.replace(/%\w+%/g, (match, idx) => { return values[i++]; }); }
this takes message to format and array of replace values and looks for%SOME_VALUE%
-
Johan Persson over 4 yearsThis lets you store the string in one place in one format and then at a later time replace the corresponding items using only one function. That is to my knowledge not possible to do with es6 template literals. A usage for this would e.g. be translation strings where you will consume the string elsewhere and inject the desired values there.
-
AMS777 about 4 yearsWith arrow function:
str.replace(/%\w+%/g, placeholder => replacements[placeholder] || placeholder);
-
Thierry J. almost 4 yearsThis solution doesn't work for runtime replacements
-
Carlos P over 3 yearsThanks for this, it's great to have a generic solution for a placeholder string and an object to merge into it like this
-
AMS777 over 3 years@CarlosP, that's the idea, to have a generic solution. But I think it should be something native to the language, as it's a common use case.
-
Kade over 3 yearsThis can be simplified a bit more by using regex groups like this:
stringWithPlaceholders.replace(/{(\w+)}/g, (fullMatch, group1) => replacements[group1] || fullMatch )
-
Jesper over 3 yearsAll these solutions suffer from the bug of using "||", since the empty string is also falsy in Javascript. This means that if a replacement string is empty, the placeholder will not be replaced. And it's absolutely valid for a replacement string to be empty. Instead of e.g.
replacements[group1] || fullMatch
, usereplacements.hasOwnProperty(group1) ? replacements[group1] : fullMatch
. -
Wyck about 3 yearsHoly injection attack vulnerability, Batman! Requires sanitizing
str
for backticks. -
Amio.io almost 3 yearsMan, can you pls include a reference link in your answer? 🙏
-
Eric almost 3 years@Amio.io You mean something like this? stravid.com/en/improve-your-javascript-console-log-friendship
-
Amio.io over 2 yearsMy bad, I thought it's from an official specification. Thx for the reference!
-
nima over 2 yearsWhile this code may answer the question, providing additional context regarding how and/or why it solves the problem would improve the answer's long-term value. You can find more information on how to write good answers in the help center: stackoverflow.com/help/how-to-answer . Good luck 🙂
-
Double_A over 2 yearsWhat if I don't want to just log the string, but keep it as value to do something else with it?
-
Jason C almost 2 yearsNode.js
util.format
supports %f.