Reputation: 65456
I'm looking for a good JavaScript equivalent of the C/PHP printf()
or for C#/Java programmers, String.Format()
(IFormatProvider
for .NET).
My basic requirement is a thousand separator format for numbers for now, but something that handles lots of combinations (including dates) would be good.
I realize Microsoft's Ajax library provides a version of String.Format()
, but we don't want the entire overhead of that framework.
Upvotes: 2460
Views: 2498804
Reputation: 3714
I am answering this question because of the following reasons.
@Braden Best and @David Spector's answers seem valid from my perspective.
I am adding the following answer so that someone can find the answer in one place.
Explanation:
:param
. Alongside that, you can pass as many replacers as you want.:param
with the currently iterating value.Mainly, if you know what Array.reduce and String.replace do, you understand the code.
You can change the :param
to anything you want. Also, you will need to change the :param
within the sprintf method in that case.
function sprintf(format, ...values) {
return values.reduce((carry, current) => carry.replace(/:param/, current), format);
}
console.log(sprintf('Hello :param! How are you :param. Are you over :param?', 'World', 'Anik', 18));
console.log(sprintf('Nothing to be replaced in here!'));
console.log(sprintf('https://httpbin.org/users/:param/posts/:param', 'anik', 'abcdefgh'));
console.log(sprintf('hello :param! How are you :param. Are you over :param? ":param"', 'world', 'anik', 18, ['extra', 'params']));
console.log(sprintf('hello :param'));
console.log(sprintf('hello', 1,2,3,4));
Upvotes: 1
Reputation: 1497
From ES6 on you could use template strings:
let soMany = 10;
console.log(`This is ${soMany} times easier!`);
// "This is 10 times easier!"
Be aware that template strings are surrounded by backticks ` instead of (single) quotes.
Note that the string is expanded immediately as soon as you define the string.
For further information:
https://developers.google.com/web/updates/2015/01/ES6-Template-Strings
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/template_strings
Note: Check the mozilla-site to find a list of supported browsers.
Upvotes: 320
Reputation: 77
Just use template strings and arrow functions. This even makes them reusable.
const greeter = (name, birthplace) => `Hi, I'm ${name}, I'm from ${birthplace}!`;
console.log( greeter('Sean', 'Earth') )
If you use the ...spread
operator you can use positioned params:
const greeterPosition = (...a) => `Hi, I'm ${a[0]}, I'm from ${a[1]}!`;
console.log( greeterPosition('Sean', 'Earth') );
You can even compose them if you like:
const greeterA = (name) => `Hi, I'm ${name}`;
const greeterB = (birthplace) => `I'm from ${birthplace}`;
const greeterComposed = (...a) => greeterA(a[0]) + ', ' + greeterB(a[1]) + '!';
console.log( greeterComposed('Sean', 'Earth') );
If you REALLY need type restrictions:
// alias the types for conciseness' sake
const [b,n,s,h] = [Boolean,Number,String,d=>Number(d).toString(16)];
const onlyBools = (...a) => `onlyBools:\nThese were Bools ${b(a[0])} ${b(a[1])}.\nThese were strings: ${b(a[2])}, ${b(a[3])}.\n...And this was a number: ${b(a[4])}`;
const onlyNumbers = (...a) => `onlyNumbers:\nThese were Bools ${n(a[0])} ${n(a[1])}.\nThese were strings: ${n(a[2])}, ${n(a[3])}.\n...And this was a number: ${n(a[4])}`;
const onlyHex = (...a) => `onlyHex:\nThese were Bools ${h(a[0])} ${h(a[1])}.\nThese were strings: ${h(a[2])}, ${h(a[3])}.\n...And this was a number: ${h(a[4])}`;
const onlyStrings = (...a) => `onlyStrings:\nThese were Bools ${s(a[0])} ${s(a[1])}.\nThese were strings: ${s(a[2])}, ${s(a[3])}.\n...And this was a number: ${s(a[4])}`;
console.log( onlyBools(true, false, 'not boolean', '', 77) );
console.log( onlyNumbers(true, false, 'not boolean', '', 77) );
console.log( onlyHex(true, false, 'not boolean', '', 77) );
console.log( onlyStrings(true, false, 'not boolean', '', 77) );
Upvotes: -1
Reputation: 303
I needed a step more forward solution.
A template I could reuse to generate strings not only in declaration but in a random time in the execution time.
So I came across with this jig:
class Texplate{
constructor(...args){
this.data = args;
}
apply(...args){
var text = "";
var i = 0, j = 0, n = this.data.length, m = args.length;
for(;i < n && j < m; i++, j++){
text += this.data[i] + args[j];
}
for (; i < n; i++){
text += this.data[i];
}
for (; j < m; j++){
text += args[j];
}
return text;
}
}
This allow to create a Text template which works internally as the array merge algorithm, starting with the text array defined in constructor.
An example of use:
var Textplate example = new Texplate("Hello, ", "!");
console.log(example.apply("Frank"));
console.log(example.apply("Mary"));
console.log(example.apply());
console.log(example.apply("Frank", " Have a good day!"));
Upvotes: 0
Reputation: 1677
Here is a very short function that does a subset of printf and shows the result in the developer console:
function L(...values)
{
// Replace each '@', starting with the text in the first arg
console.log(values.reduce(function(str,arg) {return str.replace(/@/,arg)}));
} // L
Here is a test:
let a=[1,2,3];
L('a: [@]',a);
Output is similar to: a=[1,2,3]
Upvotes: 1
Reputation: 655697
From ES6 on you could use template strings:
let soMany = 10;
console.log(`This is ${soMany} times easier!`);
// "This is 10 times easier!"
See Kim's answer below for details.
If you really want to do a simple format method on your own, don’t do the replacements successively but do them simultaneously.
Because most of the other proposals that are mentioned fail when a replace string of previous replacement does also contain a format sequence like this:
"{0}{1}".format("{1}", "{0}")
Normally you would expect the output to be {1}{0}
but the actual output is {1}{1}
. So do a simultaneous replacement instead like in fearphage’s suggestion.
Upvotes: 1645
Reputation: 379
export function stringFormat (str: string, ...args: string[]) {
return args.reduce((acc, curr, i) => acc.replace(new RegExp("\\{" + i + "\\}", 'g'), curr), str);
}
Upvotes: 0
Reputation: 70
Modified code of old answer https://stackoverflow.com/a/18234317/19531844 much more efficient (without slow RegExp) and shorter
String.prototype.formatUnicorn = function () {
let str = this.toString();
if(!arguments.length) {
return;
};
const [args] = arguments;
for (const key of Object.keys(args)) {
str = str.replaceAll(`{${key}}`, args[key]);
};
return str;
};
usage:
"{test} {test_2} {test}".formatUnicorn({"test": "hello", "test_2": "world"}); // yields hello world hello
benchmark between new and old: https://jsben.ch/BRovx
Upvotes: 1
Reputation: 3351
There are 3 different ways to format a string by replacing placeholders with the variable value.
Using template literal (backticks ``)
let name = 'John';
let age = 30;
// using backticks
console.log(`${name} is ${age} years old.`);
// John is 30 years old.
Using concatenation
let name = 'John';
let age = 30;
// using concatenation
console.log(name + ' is ' + age + ' years old.');
// John is 30 years old.
String.prototype.format = function () {
var args = arguments;
return this.replace(/{([0-9]+)}/g, function (match, index) {
// check if the argument is there
return typeof args[index] == 'undefined' ? match : args[index];
});
};
console.log('{0} is {1} years old.'.format('John', 30));
Upvotes: 35
Reputation: 24788
I use the template literal approach, like below:
export const messages = {
foo: (arg1, arg2) => `Hello ${arg1} ${arg2}`,
bar: (arg1) => `Hello ${arg1}`,
}
From the file:
console.log(messages.foo('Bar', 'World'))
console.log(messages.bar('Foo'))
Upvotes: 1
Reputation: 4422
For Node.js users there is util.format
which has printf-like functionality:
util.format("%s world", "Hello")
Upvotes: 133
Reputation: 472
Ok, so first we'll set up some variables to use:
const date = new Date();
const locale = 'en-us';
const wDay = date.toLocaleString(locale, {weekday: 'short'});
const month = date.toLocaleString(locale, {month: 'long'});
const year = date.toLocaleString(locale, {year: 'numeric'});
const minute = date.toLocaleString(locale, {minute: 'numeric'});
const [hour, ap] = date.toLocaleString(locale, {hour: 'numeric', hour12:true}).split(' ');
let mDay = date.toLocaleString(locale, {day: 'numeric'});
switch(mDay % 10)
{
case 1: mDay += 'st'; break;
case 2: mDay += 'nd'; break;
case 3: mDay += 'rd'; break;
default: mDay += 'th'; break;
}
Now that we've got all that, we can format a string like so:
const formatter = (...a) => `${a[0]}, the ${a[1]} of ${a[2]} ${a[3]} at ${a[4]}:${a[5]} ${a[6]}`;
const formatted = formatter(wDay, mDay, month, year, hour, minute, ap);
We could even use named paramaters for the "formatter" function:
const formatter = (wDay, mDay, month, year, hour, minute, ap) => `${wDay}, the ${mDay} of ${month} ${year} at ${hour}:${minute} ${ap}`;
const formatted = formatter(wDay, mDay, month, year, hour, minute, ap);
If you'll notice, the JS templates above are both the results of callbacks. If the entire piece of code above were encapsulated within a function that was expected to return a formatted date, it would not be hard to imagine how to construct an arbitrary "formatter" function in the same manner, that could be passed in from outside.
tl;dr you can re-use template literals if you put them inside callbacks and use the args as the replacements.
Upvotes: 0
Reputation: 3129
Right now, there is a package called locutus which translate the functions of other languages to Javascript such as php, python, ruby etc.
const printf = require('locutus/php/strings/printf')
printf('Hello world');
You can try this playground codesandbox
Upvotes: 0
Reputation: 402
If you need a printf, use printf
Looks like 90% of commenters never used printf with more complex format than just %d. I wonder how do they output, for example, money values?
Upvotes: 0
Reputation: 978
It's funny because Stack Overflow actually has their own formatting function for the String
prototype called formatUnicorn
. Try it! Go into the console and type something like:
"Hello, {name}, are you feeling {adjective}?".formatUnicorn({name:"Gabriel", adjective: "OK"});
You get this output:
Hello, Gabriel, are you feeling OK?
You can use objects, arrays, and strings as arguments! I got its code and reworked it to produce a new version of String.prototype.format
:
String.prototype.formatUnicorn = String.prototype.formatUnicorn ||
function () {
"use strict";
var str = this.toString();
if (arguments.length) {
var t = typeof arguments[0];
var key;
var args = ("string" === t || "number" === t) ?
Array.prototype.slice.call(arguments)
: arguments[0];
for (key in args) {
str = str.replace(new RegExp("\\{" + key + "\\}", "gi"), args[key]);
}
}
return str;
};
Note the clever Array.prototype.slice.call(arguments)
call -- that means if you throw in arguments that are strings or numbers, not a single JSON-style object, you get C#'s String.Format
behavior almost exactly.
"a{0}bcd{1}ef".formatUnicorn("FOO", "BAR"); // yields "aFOObcdBARef"
That's because Array
's slice
will force whatever's in arguments
into an Array
, whether it was originally or not, and the key
will be the index (0, 1, 2...) of each array element coerced into a string (eg, "0", so "\\{0\\}"
for your first regexp pattern).
Neat.
Upvotes: 670
Reputation: 276
sprintf() function analog in JavaScript as Vue filter and String.prototype.format() extension:
/**
* Returns a formatted string.
*
* @param template
* @param values
* @return string
*/
String.format = function (template, ...values) {
let i = -1;
function callback(exp, p0, p1, p2, p3, p4) {
if (exp === '%%') return '%';
if (values[++i] === undefined) return undefined;
exp = p2 ? parseInt(p2.substr(1)) : undefined;
let base = p3 ? parseInt(p3.substr(1)) : undefined;
let val;
switch (p4) {
case 's': val = values[i]; break;
case 'c': val = values[i][0]; break;
case 'f': val = parseFloat(values[i]).toFixed(exp); break;
case 'p': val = parseFloat(values[i]).toPrecision(exp); break;
case 'e': val = parseFloat(values[i]).toExponential(exp); break;
case 'x': val = parseInt(values[i]).toString(base ? base : 16); break;
case 'd': val = parseFloat(parseInt(values[i], base ? base : 10).toPrecision(exp)).toFixed(0); break;
}
val = typeof (val) == 'object' ? JSON.stringify(val) : val.toString(base);
let sz = parseInt(p1); /* padding size */
let ch = p1 && p1[0] === '0' ? '0' : ' '; /* isnull? */
while (val.length < sz) val = p0 !== undefined ? val + ch : ch + val; /* isminus? */
return val;
}
let regex = /%(-)?(0?[0-9]+)?([.][0-9]+)?([#][0-9]+)?([scfpexd%])/g;
return template.replace(regex, callback);
}
String.prototype.format = function() {
return String.format(this, ...arguments);
}
const StringFormat = {
install: (Vue, options) => {
Vue.filter('format', function () {
return String.format(...arguments);
});
},
};
export default StringFormat;
Original answer: JavaScript equivalent to printf/String.Format
Upvotes: -1
Reputation: 1577
if you just need to format a string with %s specifier only
function _sprintf(message){
const regexp = RegExp('%s','g');
let match;
let index = 1;
while((match = regexp.exec(message)) !== null) {
let replacement = arguments[index];
if (replacement) {
let messageToArray = message.split('');
messageToArray.splice(match.index, regexp.lastIndex - match.index, replacement);
message = messageToArray.join('');
index++;
} else {
break;
}
}
return message;
}
_sprintf("my name is %s, my age is %s", "bob", 50); // my name is bob, my age is 50
Upvotes: 0
Reputation:
Not the most recommended function in the world, but it works.
If you need sprintf, just copy & paste this same function and change return console.log(sb)
to just return sb
.
printf = function(s, /*args...*/) {
a = arguments;
al = a.length;
if (al <= 1) return -2;
if (al >= 2 && s.toLowerCase().search(/%[a-z]/) == -1) return -1;
sb = s;
for (i = 1; i <= al - 1; i++) {
sb = sb.replace(/%[a-z]/, a[i]);
}
return console.log(sb);
}
var someString = "Hello %s\nIt's %s:%s %s now.\nThe day is %s\n";
printf(someString, "StackOverflowUser", "5", "48", "PM", "beautiful");
Upvotes: -1
Reputation: 1683
another suggestion is you use the string template:
const getPathDadosCidades = (id: string) => `/clientes/${id}`
const getPathDadosCidades = (id: string, role: string) => `/clientes/${id}/roles/${role}`
Upvotes: 3
Reputation: 4279
In typescript create a file named format.ts
and import it whatever you need to use formatting.
// contents of format.ts
interface String {
format(...args: any[]): string;
}
if (!String.prototype.format) {
String.prototype.format = function() {
let a = this;
let b: any;
// tslint:disable-next-line: forin
for (b in arguments) {
a = a.replace(/%[a-z]/, arguments[b]);
}
return a;
};
}
To format string use this code:
import './format';
console.log('Hello, %s!'.format('World'));
String.prototype.format = function() {
let a = this;
let b;
for (b in arguments) {
a = a.replace(/%[a-z]/, arguments[b]);
}
return a;
};
console.log('Hello, %s!'.format('World'));
Upvotes: 0
Reputation: 8915
I got to this question page hoping to find how to format numbers in JavaScript, without introducing yet another library. Here's what I've found:
The equivalent of sprintf("%.2f", num)
in JavaScript seems to be num.toFixed(2)
, which formats num
to 2 decimal places, with rounding (but see @ars265's comment about Math.round
below).
(12.345).toFixed(2); // returns "12.35" (rounding!)
(12.3).toFixed(2); // returns "12.30" (zero padding)
The equivalent of sprintf("%.2e", num)
is num.toExponential(2)
.
(33333).toExponential(2); // "3.33e+4"
To print numbers in base B, try num.toString(B)
. JavaScript supports automatic conversion to and from bases 2 through 36 (in addition, some browsers have limited support for base64 encoding).
(3735928559).toString(16); // to base 16: "deadbeef"
parseInt("deadbeef", 16); // from base 16: 3735928559
Quick tutorial on JS number formatting
Mozilla reference page for toFixed() (with links to toPrecision(), toExponential(), toLocaleString(), ...)
Upvotes: 363
Reputation: 4315
We can use a simple lightweight String.Format string operation library for Typescript.
String.Format():
var id = image.GetId()
String.Format("image_{0}.jpg", id)
output: "image_2db5da20-1c5d-4f1a-8fd4-b41e34c8c5b5.jpg";
String Format for specifiers:
var value = String.Format("{0:L}", "APPLE"); //output "apple"
value = String.Format("{0:U}", "apple"); // output "APPLE"
value = String.Format("{0:d}", "2017-01-23 00:00"); //output "23.01.2017"
value = String.Format("{0:s}", "21.03.2017 22:15:01") //output "2017-03-21T22:15:01"
value = String.Format("{0:n}", 1000000);
//output "1.000.000"
value = String.Format("{0:00}", 1);
//output "01"
String Format for Objects including specifiers:
var fruit = new Fruit();
fruit.type = "apple";
fruit.color = "RED";
fruit.shippingDate = new Date(2018, 1, 1);
fruit.amount = 10000;
String.Format("the {type:U} is {color:L} shipped on {shippingDate:s} with an amount of {amount:n}", fruit);
// output: the APPLE is red shipped on 2018-01-01 with an amount of 10.000
Upvotes: 6
Reputation: 525
String.prototype.format = function(){
var final = String(this);
for(let i=0; i<arguments.length;i++){
final = final.replace(`%s${i+1}`, arguments[i])
}
return final || ''
}
console.log(("hello %s2 how %s3 you %s1").format('hi', 'hello', 'how'));
<h1 id="text">
</h1>
Upvotes: 1
Reputation: 8998
Adding to zippoxer
's answer, I use this function:
String.prototype.format = function () {
var a = this, b;
for (b in arguments) {
a = a.replace(/%[a-z]/, arguments[b]);
}
return a; // Make chainable
};
var s = 'Hello %s The magic number is %d.';
s.format('world!', 12); // Hello World! The magic number is 12.
I also have a non-prototype version which I use more often for its Java-like syntax:
function format() {
var a, b, c;
a = arguments[0];
b = [];
for(c = 1; c < arguments.length; c++){
b.push(arguments[c]);
}
for (c in b) {
a = a.replace(/%[a-z]/, b[c]);
}
return a;
}
format('%d ducks, 55 %s', 12, 'cats'); // 12 ducks, 55 cats
All the cool new stuff in ES 2015 makes this a lot easier:
function format(fmt, ...args){
return fmt
.split("%%")
.reduce((aggregate, chunk, i) =>
aggregate + chunk + (args[i] || ""), "");
}
format("Hello %%! I ate %% apples today.", "World", 44);
// "Hello World, I ate 44 apples today."
I figured that since this, like the older ones, doesn't actually parse the letters, it might as well just use a single token %%
. This has the benefit of being obvious and not making it difficult to use a single %
. However, if you need %%
for some reason, you would need to replace it with itself:
format("I love percentage signs! %%", "%%");
// "I love percentage signs! %%"
Upvotes: 28
Reputation: 19
I want to share my solution for the 'problem'. I haven't re-invented the wheel but tries to find a solution based on what JavaScript already does. The advantage is, that you get all implicit conversions for free. Setting the prototype property $ of String gives a very nice and compact syntax (see examples below). It is maybe not the most efficient way, but in most cases dealing with output it does not have to be super optimized.
String.form = function(str, arr) {
var i = -1;
function callback(exp, p0, p1, p2, p3, p4) {
if (exp=='%%') return '%';
if (arr[++i]===undefined) return undefined;
exp = p2 ? parseInt(p2.substr(1)) : undefined;
var base = p3 ? parseInt(p3.substr(1)) : undefined;
var val;
switch (p4) {
case 's': val = arr[i]; break;
case 'c': val = arr[i][0]; break;
case 'f': val = parseFloat(arr[i]).toFixed(exp); break;
case 'p': val = parseFloat(arr[i]).toPrecision(exp); break;
case 'e': val = parseFloat(arr[i]).toExponential(exp); break;
case 'x': val = parseInt(arr[i]).toString(base?base:16); break;
case 'd': val = parseFloat(parseInt(arr[i], base?base:10).toPrecision(exp)).toFixed(0); break;
}
val = typeof(val)=='object' ? JSON.stringify(val) : val.toString(base);
var sz = parseInt(p1); /* padding size */
var ch = p1 && p1[0]=='0' ? '0' : ' '; /* isnull? */
while (val.length<sz) val = p0 !== undefined ? val+ch : ch+val; /* isminus? */
return val;
}
var regex = /%(-)?(0?[0-9]+)?([.][0-9]+)?([#][0-9]+)?([scfpexd%])/g;
return str.replace(regex, callback);
}
String.prototype.$ = function() {
return String.form(this, Array.prototype.slice.call(arguments));
}
Here are a few examples:
String.format("%s %s", [ "This is a string", 11 ])
console.log("%s %s".$("This is a string", 11))
var arr = [ "12.3", 13.6 ]; console.log("Array: %s".$(arr));
var obj = { test:"test", id:12 }; console.log("Object: %s".$(obj));
console.log("%c", "Test");
console.log("%5d".$(12)); // ' 12'
console.log("%05d".$(12)); // '00012'
console.log("%-5d".$(12)); // '12 '
console.log("%5.2d".$(123)); // ' 120'
console.log("%5.2f".$(1.1)); // ' 1.10'
console.log("%10.2e".$(1.1)); // ' 1.10e+0'
console.log("%5.3p".$(1.12345)); // ' 1.12'
console.log("%5x".$(45054)); // ' affe'
console.log("%20#2x".$("45054")); // ' 1010111111111110'
console.log("%6#2d".$("111")); // ' 7'
console.log("%6#16d".$("affe")); // ' 45054'
Upvotes: 28
Reputation: 41430
Using Lodash you can get template functionality:
Use the ES template literal delimiter as an "interpolate" delimiter. Disable support by replacing the "interpolate" delimiter.
var compiled = _.template('hello ${ user }!');
compiled({ 'user': 'pebbles' });
// => 'hello pebbles!
Upvotes: 5
Reputation: 4996
I'm surprised no one used reduce
, this is a native concise and powerful JavaScript function.
String.prototype.format = function() {
return [...arguments].reduce((p,c) => p.replace(/%s/,c), this);
};
console.log('Is that a %s or a %s?... No, it\'s %s!'.format('plane', 'bird', 'SOman'));
function interpolate(theString, argumentArray) {
var regex = /%s/;
var _r=function(p,c){return p.replace(regex,c);}
return argumentArray.reduce(_r, theString);
}
interpolate("%s, %s and %s", ["Me", "myself", "I"]); // "Me, myself and I"
How it works:
reduce applies a function against an accumulator and each element in the array (from left to right) to reduce it to a single value.
var _r= function(p,c){return p.replace(/%s/,c)};
console.log(
["a", "b", "c"].reduce(_r, "[%s], [%s] and [%s]") + '\n',
[1, 2, 3].reduce(_r, "%s+%s=%s") + '\n',
["cool", 1337, "stuff"].reduce(_r, "%s %s %s")
);
Upvotes: 69
Reputation: 65456
I'll add my own discoveries which I've found since I asked:
Sadly it seems sprintf doesn't handle thousand separator formatting like .NET's string format.
Upvotes: 14
Reputation: 1135
JavaScript programmers can use String.prototype.sprintf at https://github.com/ildar-shaimordanov/jsxt/blob/master/js/String.js. Below is example:
var d = new Date();
var dateStr = '%02d:%02d:%02d'.sprintf(
d.getHours(),
d.getMinutes(),
d.getSeconds());
Upvotes: 32
Reputation: 16948
Building on the previously suggested solutions:
// First, checks if it isn't implemented yet.
if (!String.prototype.format) {
String.prototype.format = function() {
var args = arguments;
return this.replace(/{(\d+)}/g, function(match, number) {
return typeof args[number] != 'undefined'
? args[number]
: match
;
});
};
}
"{0} is dead, but {1} is alive! {0} {2}".format("ASP", "ASP.NET")
outputs
ASP is dead, but ASP.NET is alive! ASP {2}
If you prefer not to modify String
's prototype:
if (!String.format) {
String.format = function(format) {
var args = Array.prototype.slice.call(arguments, 1);
return format.replace(/{(\d+)}/g, function(match, number) {
return typeof args[number] != 'undefined'
? args[number]
: match
;
});
};
}
Gives you the much more familiar:
String.format('{0} is dead, but {1} is alive! {0} {2}', 'ASP', 'ASP.NET');
with the same result:
ASP is dead, but ASP.NET is alive! ASP {2}
Upvotes: 1510