Apply template string dynamically in JavaScript [duplicate] - javascript

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.

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;
});
jsFiddle.
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.
jsFiddle.
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.
jsFiddle.
Otherwise, if your replacement string was 'hasOwnProperty', you would get a resultant messed up string.
jsFiddle.
As a side note, you should be called replacements an Object, not an Array.

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

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);

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

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 or F12 to open developer tool.
In Firefox, use shortcut Ctrl + Shift + K or F12 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.

Just use replace()
var values = {"%NAME%":"Mike","%AGE%":"26","%EVENT%":"20"};
var substitutedString = "My Name is %NAME% and my age is %AGE%.".replace("%NAME%", $values["%NAME%"]).replace("%AGE%", $values["%AGE%"]);

You can use a custom replace function like this:
var str = "My Name is %NAME% and my age is %AGE%.";
var replaceData = {"%NAME%":"Mike","%AGE%":"26","%EVENT%":"20"};
function substitute(str, data) {
var output = str.replace(/%[^%]+%/g, function(match) {
if (match in data) {
return(data[match]);
} else {
return("");
}
});
return(output);
}
var output = substitute(str, replaceData);
You can see it work here: http://jsfiddle.net/jfriend00/DyCwk/.

If you want to do something closer to console.log like replacing %s placeholders like in
>console.log("Hello %s how are you %s is everything %s?", "Loreto", "today", "allright")
>Hello Loreto how are you today is everything allright?
I wrote this
function log() {
var args = Array.prototype.slice.call(arguments);
var rep= args.slice(1, args.length);
var i=0;
var output = args[0].replace(/%s/g, function(match,idx) {
var subst=rep.slice(i, ++i);
return( subst );
});
return(output);
}
res=log("Hello %s how are you %s is everything %s?", "Loreto", "today", "allright");
document.getElementById("console").innerHTML=res;
<span id="console"/>
you will get
>log("Hello %s how are you %s is everything %s?", "Loreto", "today", "allright")
>"Hello Loreto how are you today is everything allright?"
UPDATE
I have added a simple variant as String.prototype useful when dealing with string transformations, here is it:
String.prototype.log = function() {
var args = Array.prototype.slice.call(arguments);
var rep= args.slice(0, args.length);
var i=0;
var output = this.replace(/%s|%d|%f|%#/g, function(match,idx) {
var subst=rep.slice(i, ++i);
return( subst );
});
return output;
}
In that case you will do
"Hello %s how are you %s is everything %s?".log("Loreto", "today", "allright")
"Hello Loreto how are you today is everything allright?"
Try this version here

This allows you to do exactly that
NPM: https://www.npmjs.com/package/stringinject
GitHub: https://github.com/tjcafferkey/stringinject
By doing the following:
var str = stringInject("My username is {username} on {platform}", { username: "tjcafferkey", platform: "GitHub" });
// My username is tjcafferkey on Git

I have written a code that lets you format string easily.
Use this function.
function format() {
if (arguments.length === 0) {
throw "No arguments";
}
const string = arguments[0];
const lst = string.split("{}");
if (lst.length !== arguments.length) {
throw "Placeholder format mismatched";
}
let string2 = "";
let off = 1;
for (let i = 0; i < lst.length; i++) {
if (off < arguments.length) {
string2 += lst[i] + arguments[off++]
} else {
string2 += lst[i]
}
}
return string2;
}
Example
format('My Name is {} and my age is {}', 'Mike', 26);
Output
My Name is Mike and my age is 26

Another solution if you're using node.js is StackExchange's own formatUnicorn utility function (https://www.npmjs.com/package/format-unicorn):
let x = {name:'jason', food:'pine cones'};
let s = '{name} enjoys a delicious bowl of {food}';
let formatted = x.formatUnicorn(s);
Also, a bit of an edge case, but if you aren't using Node but you do just happen to be writing a userscript for SE sites, then formatUnicorn will already be on the String prototype.

As a quick example:
var name = 'jack';
var age = 40;
console.log('%s is %d yrs old',name,age);
The output is:
jack is 40 yrs old

Here is another way of doing this by using es6 template literals dynamically at runtime.
const str = 'My name is ${name} and my age is ${age}.'
const obj = {name:'Simon', age:'33'}
const result = new Function('const {' + Object.keys(obj).join(',') + '} = this.obj;return `' + str + '`').call({obj})
document.body.innerHTML = result

const stringInject = (str = '', obj = {}) => {
let newStr = str;
Object.keys(obj).forEach((key) => {
let placeHolder = `#${key}#`;
if(newStr.includes(placeHolder)) {
newStr = newStr.replace(placeHolder, obj[key] || " ");
}
});
return newStr;
}
Input: stringInject("Hi #name#, How are you?", {name: "Ram"});
Output: "Hi Ram, How are you?"

ES6:
const strFormat = (str, ...args) => args.reduce((s, v) => s.replace('%s', v), str);
// Use it like:
const result = strFormat('%s is %s yrs old', 'name', 23);

Lots of good/similar answers here. I wanted the ability to easily get a nested key in an object (or perhaps some JSON data structure) for substitution, so I took the following simple approach:
const getKey = (d, path) => {
// path can be a string like 'key1.key2' or an iterable of keys
if (typeof(path) === 'string') {
path = path.split('.')
}
return path.reduce((x, y) => x[y], d)
}
const inject = (str, obj) => str.replace(/\${(.*?)}/g, (x,g)=> getKey(obj, g));
// Example
> const str = 'there are ${a} ways to ${b.c}'
undefined
> inject(str, {'a':'many', 'b': {'c': 'skin a cat'}})
'there are many ways to skin a cat'
Some inspiration from this and this.

This is a merged solution of Gerson Diniz and Shubham Vyas.
It is possible to pass a set of arguments or an object.
function strSwap(str) {
if (!str) return null;
let args = [];
for (let a of arguments)
args.push(a);
args.shift();
if (!args.length) return null;
// replacement by object - {{prop}}
if (!!(args[0].constructor && args[0].constructor.name.toLowerCase() === 'object')) {
for (let i in args[0]) {
let n = `{{${i}}}`;
str = str.includes(n) ? str.replaceAll(n, args[0][i] + '') : str;
}
}
// replacement by placeholders - %s
else {
str = args.reduce((s, v) => s.replace('%s', v), str);
}
return str;
}
// ---------------------
console.log(strSwap('Hello %s, my name is %s.', 'alice', 'bob'));
console.log(strSwap('Hello {{a}}, my name is {{b}}. Hello {{b}}.', {
a: 'alice',
b: 'bob'
}));

Related

How to replace specific parts of a json file using node.js

Currently, I have a JSON file that looks like this:
"1E10BC5D4EC68EE2916BFD97F23E951C": "Seattle Seahawks",
"E6B87019417436B73B62F7802763A289": "Seaside style. ",
"81EEA9E6400BFEADE161559AF14EE468": " {1}",
"6F02148E4A78B33C1CEB75BC2753CA69": " {EndDate}",
"D89CA2634FFF8FA02D028096BAAE6963": "\"You have received a reward for completing the {Bundle Name} {Number} challenges!",
"Mission": {
"Default Mission Info Description": "Default Mission Description",
"Default Mission Info Name": "Default Mission Name",
"RewardDescription": "You ranked {PositionRank}. For your efforts, you have been awarded:",
"NonParticipationRewardDescription": "Your teammates did a great job! For their efforts, you have been awarded:",
"RewardTitle": "{MissionName} Completed!"
}
It goes on for about 40,000 lines, and I would like to modify all of the strings set by it. Currently, I am using #zuzak/owo to try to accomplish this. My current code looks like this:
const owo = require('#zuzak/owo')
fs = require('fs');
var name = '../jsonfile.json';
var data = fs.readFileSync(name).toString();
fs.writeFileSync('../owo.json', JSON.stringify(owo(data)))
How would I be able to only change the strings, such as "Seaside style. " and not edit any of the string names, such as "81EEA9E6400BFEADE161559AF14EE468" (sorry for any incorrect wording, hopefully you can understand what I am saying.)
In case you need it, here is the main code used in #zuzak/owo:
const addAffixes = (str) => randomItem(prefixes) + str + randomItem(suffixes)
const substitute = (str) => {
const replacements = Object.keys(substitutions)
replacements.forEach((x) => {
str = replaceString(str, x, substitutions[x])
})
return str
}
Parse the JSON, iterate over keys, modify values. If necessary recursively iterate over subobjects too:
function owoifyObject (obj) {
for (const key in obj) {
if (typeof obj[key] === 'string') {
obj[key] = owo(obj[key])
} else if (typeof obj[key] === 'object' && obj[key]) {
owoifyObject(obj[key])
}
}
}
const data = JSON.parse(fs.readFileSync('file.json').toString())
owoifyObject(data)
fs.writeFileSync('file2.json', JSON.stringify(data, null, 4))
Note that the argument 4 in JSON.stringify is purely there for formatting so that the result looks something like your input did, otherwise you'd get all the data in a single line.
Use JSON.parse() to parse the JSON file into an object. Then go through the object calling owo() on each value.
var data = JSON.parse(fs.readFileSync(name).toString());
for (let key in data) {
data[key] = owo(data[key]);
}
fs.writeFileSync("../owo.json", JSON.stringify(data));

Replace = with : in serialized data

i'm recieving a serialzed data in the form as
error_message=%7B%0D%0A+null+%7D&systemId=53183&cert-id=176061&score=0&q2c=3%5C0&q2t=&answer_data=0000
how can i replace the = with : using typescript and replace should be for all the occurence of = except first that is error_message=value&systemId:value&cert-id=value and so on
i was trying with splice but it seems to be a long process and not that fast as the string grows.
You can do that by splitting the string by = and then treating the first element different from all others:
const input = 'error_message=%7B%0D%0A+null+%7D&systemId=53183&cert-id=176061&score=0&q2c=3%5C0&q2t=&answer_data=0000';
const repl = (inp) => {
const [firstEl, ...rest] = inp.split(/\=/); // Split by '='
return `${firstEl}=${rest.join(':')}`; // <first el> + '=' + <all other elements joined by ':'>
}
console.log(repl(input));
It looks like these are URL search query parameters, so you can get them in a more manageable format using the URLSearchParams constructor, then manipulate them however you want:
const queryString = 'error_message=%7B%0D%0A+null+%7D&systemId=53183&cert-id=176061&score=0&q2c=3%5C0&q2t=&answer_data=0000'
const [ first, ...rest ] = [...new URLSearchParams(queryString)]
.map(([k, v]) => [k, v].map(encodeURIComponent))
// assuming you still want URI encoding, else remove mapping
const result = [
first.join(':'),
...rest.map(x => x.join('=')),
].join('&')
console.log(result)
You can get the first '=' with the method indexOf(), then slice the string into the two different parts (one before the first '=' and one after) and replace the following equal signs
const string = "error_message=%7B%0D%0A+null+%7D&systemId=53183&cert-id=176061&score=0&q2c=3%5C0&q2t=&answer_data=0000"
const firstSignIndex = string.indexOf('=')
const result = string.slice(0, firstSignIndex+1) + string.slice(firstSignIndex+1).replace(/=/g, ':')
console.log(result)
Check this out, this will help you to replace the = with : except the first occurrence of = in your serialized string.
let a = 'error_message=%7B%0D%0A+null+%7D&systemId=53183&cert-id=176061&score=0&q2c=3%5C0&q2t=&answer_data=0000',
firstOcc = a.indexOf("error_message=") + 'error_message='.length,
starterStr = a.substring(0, firstOcc),
replacedStr = a.substring(firstOcc, a.length).replace(/=/g, ':');
console.log(starterStr + replacedStr);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
You can make changes based on your needs firstOcc = a.indexOf("error_message=") + 'error_message='.length,

Replace object keys with values in given string pattern

I have a task to replace all keys in string pattern with their values. The input is something like that:
[
'{ "name": "John", "age": 13 }',
"My name is #{name} and I am #{age}-years-old"
]
And the output is this: 'My name is John and I am 13-years-old'.
So I come up with this:
function FillTemplate() {
if (arguments.length < 2 || arguments.length > 7) {
console.log('The input objects should be at least 1 and lesser than 7!');
}
for (let i = 0; i <= arguments.length - 2; i += 1) {
JSON.parse(arguments[i]);
for (let j = 0; j < Object.keys(arguments[i]).length; i += 1) {
let currentKey = Object.keys(arguments[i])[j];
console.log(currentKey);
}
}
}
I have a problem when i console.log(currentKey) i got only zeros but my idea is take the first object in the input then json.parse it next take all the keys in that object and with one loop take every single key separately and replace it in the pattern string with regex pattern. But this Object.keys return only zeros to me. Where is the problem?
Here you go:
<script>
var foo = {
"name" : "John",
"age" : 13
}
var string = "My name is #{name} and I am #{age}-years-old";
// Extract all templates (#{name}, #{age}, ...)
var matches = string.match(/#{[a-zA-Z]+?}/g);
if ( matches ) {
matches.forEach(function(templateStringToReplace) {
// Strip the special characters to dynamically get the indices of the object
templateString = templateStringToReplace.replace(/#|{|}/g, "");
string = string.replace(templateStringToReplace, foo[templateString])
});
}
alert(string);
Try the other way around, parse the template string first, then loop over the keys you need so you can reference them directly in the object.
Also, I have no idea what you're trying to do with the arguments object.
// Our source array containing a data string and a template string
var source = [
'{"name": "John", "age": 13 }',
'My name is #{name} and I am #{age}-years-old'
],
// helper function to grab all the parameters from a template string
parseTemplate = function parseTemplate( template ) {
var regex = /#\{(.+?)\}/g,
parameters = [],
nextParameter;
do {
// this regexp will grab the next series of characters surrounded by #{}
nextParameter = regex.exec(template);
if (nextParameter) parameters.push(nextParameter[1]);
}
// as long as there are parameters left, keep searching
while (nextParameter);
return parameters;
},
// population function, uses parseTemplate to get the parameters and then adds them to the template
populateTemplate = function populate( template, data ) {
var parametersToSaturate = parseTemplate(template);
// for each parameter found, repalce that parameter in the string with the value from the data
return parametersToSaturate.reduce(function( saturatedTemplate, parameter ) {
return saturatedTemplate.replace('#{' + parameter + '}', data[parameter] || ('#{' + parameter + '}'));
}, template);
},
result = populateTemplate( source[1], JSON.parse(source[0]) );
console.log(result);
As long as you keep the array returned from parseTemplate is the same order, you can reuse any parameter as many times in a string as you want. Any #{val} parameter not found in the data will just remain.
If you have multiple objects, you can just loop over them.
sources.forEach(function( source ) {
console.log(populateTemplate( source[1], JSON.parse(source[0]) ));
});
If your browser supports it, you can use actual JS template strings:
https://developer.mozilla.org/nl/docs/Web/JavaScript/Reference/Template_literals

Import text json for node js template literals

For my node js projects i typically have a text.json file and require it, instead of having static text within my code. something like below
JSON file
{
"greet":"Hello world"
}
var text = require('./text.json');
var greet = text.greet
I am having a little trouble in figuring out how this would work with template literals ?
I know this is an old issue but I just came up with a need for the same thing and.. yeah there are node modules that help do this but, this isn't that complex so I just made my own solution
function injectVariables( replacements, input ) {
const entries = Object.entries(replacements)
const result = entries.reduce( (output, entry) => {
const [key, value] = entry
const regex = new RegExp( `\\$\{${key}\}`, 'g')
return output.replace( regex, value )
}, input )
return result
}
const template = 'Hello my name is ${name} and I like ${language}'
const inputs = { name: 'David', language: 'JavaScript' }
const replaced = injectVariables(inputs, template)
console.log(replaced)
So, in this, it takes an input string and an object where the keys are the variable names in the string and the values are, you guessed it, the values.
It creates an array the values using Object.entries and then runs reduce across the entries to keep an updated version of the string as you go. On each iteration it makes a regex to match the variable expression and replaces that value with the one passed it.
This in particular won't look through nested objects (I didn't need that) but if for example your string had ${name.last} in it, since object keys can be strings, your input variable could be inputs = { 'name.last': 'Smith' } and it should work.
Hopefully this helps someone else.
I often use a very tiny templating helper library (tim - https://github.com/premasagar/tim) and it can be used to accomplish this:
//in my json file
var strings = {
'Hello': 'Hello {{name}}!',
'Goodbye': 'Goodbye {{name}}!'
};
//in my app
var tim = require('tim'); //templating library
var strings = require('./strings.json');
//replace
console.log(tim(strings.Hello,{name:'Fred'}));
Relevant JSFiddle:
https://jsfiddle.net/rtresjqv/
Alternatively, you could turn your strings into functions and then pass in the arguments:
//in my json file
var strings = {
'Hello': function() { return `Hello ${arguments[0]}!`; },
'Goodbye': function() { return `Goodbye {$arguments[0]}!`; }
};
//in my app
var strings = require('./strings.json');
//replace
console.log(strings.Hello('Fred'));
Fiddle:
https://jsfiddle.net/t6ta0576/

Javascript regExp - replace function should decide not to replace matched string to let other parenthesized submatch string work with a match

Here is a very simple example of what I want to do.
Suppose I have this code:
function myReplaceFunction(match, p1, p2) {
// If "(fooball:)" got a match
if (p1) {
// Just some condition. This one is stupid, but you get the idea.
if (match === "football:") {
// Don't do anything. Can I not replace from function?
return match;
}
} else if (p2) { // If "(ball:)" got a match
if (match === "ball:") {
return "step:";
}
}
};
var regexp = /(football:)|(ball:)/g;
var originalString = 'something football: something';
var newString = originalString.replace(regexp, myReplaceFunction);
console.log(newString);
The result: "something football: something"
I want: "something footstep: something"
After (football:) gets a match, my function should decide not to replace "football:" with anything, BUT I do want the second parenthesized submatch string "ball:" to replace "ball:" with "step:", so the result would be "something footstep: something".
I need to give a (ball:) chance to work with the original string too...
Can I achieve this? If question is unclear, please ask, I'll explain.
Thank you!
Updated: Changed based on your link in comment.
Give this a try. I did build a fast lookup from displayName to systemName to use:
var fieldNames = [
{ displayName: "Sender", systemName: "from_field"},
{ displayName: "Receiver(addressee)", systemName: "to_field"},
{ displayName: "Author", systemName: "author_id_field"}
],
regex = /(?:#?"?)([^":]+)(?:"?):/i,
map = {
"Sender": "from_field",
"Receiver(addressee)": "to_field",
"Author": "author_id_field"
};
var test = [
'#"Sender":',
'"Sender":',
'Sender:',
'#"Receiver(addressee)":',
'"Receiver(addressee)":',
'Receiver(addressee):',
'#"Unknown":'
];
for (var i=0; i<test.length; i++) {
var systemName = test[i].replace(regex, function(match, p1) {
if (p1) return map[p1] || p1;
});
console.log("Display[%s] = System[%s]", test[i], systemName);
}
// output:
// Display[#"Sender":] = System[from_field]
// Display["Sender":] = System[from_field]
// Display[Sender:] = System[from_field]
// Display[#"Receiver(addressee)":] = System[to_field]
// Display["Receiver(addressee)":] = System[to_field]
// Display[Receiver(addressee):] = System[to_field]
// Display[#"Unknown":] = System[Unknown]
If the displayname being matched isn't known (_i.e. in the lookup map), it simply returns it as is.

Categories