2442

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.

8
  • 4
    Aside all the great answers below, you may want to take a look at this one: stackoverflow.com/a/2648463/1712065 which IMO, is the most efficient solution to this problem.
    – Annie
    Commented Apr 24, 2015 at 2:54
  • 3
    I wrote a cheap one that uses C-like printf syntax. Commented Jan 28, 2016 at 19:38
  • var search = [$scope.dog, "1"]; var url = vsprintf("earth/Services/dogSearch.svc/FindMe/%s/%s", search); ***For node, you can get your module by "npm install sprintf-js"
    – Jenna Leaf
    Commented Apr 29, 2016 at 19:25
  • 6
    Most of the answers here are disappointing. Both printf and String.Format are way more than just simple templating, and the question specifically mentions thousand separators, which none of the simple templating solutions handle.
    – blm
    Commented Jan 23, 2021 at 23:01
  • 1
    Besides template string; String.padStart might be the other thing ppl looking for. (See stackoverflow.com/questions/2686855/… )
    – Nor.Z
    Commented Feb 19, 2023 at 12:39

61 Answers 61

1636

Current JavaScript

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.


Older answer

Try sprintf() for JavaScript.


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.

15
  • 30
    If only some simple number-to-string conversion is desired, num.toFixed() method might be enough! Commented Dec 17, 2012 at 23:29
  • 2
    @MaksymilianMajer that seems to be something massively different. Commented Apr 14, 2014 at 21:51
  • @EvanCarroll you are right. At the time I wrote the comment the repository of sprintf() for JavaScript was not available. underscore.string has more features aside from sprintf which is based on sprintf() for JavaScript implementation. Other than that the library is an entirely different project. Commented Apr 15, 2014 at 7:41
  • 4
    This shouldn't be accepted answer anymore. As of ES6 this is built into the javascript language (both in browsers and NodeJS). See @Kim 's answer below. Commented Jul 2, 2018 at 19:17
  • 7
    Interpolated strings aren't a good solution for template strings that are not known at development time. For example, your page might retrieve a template string from a web server, then populate it with certain variables.
    – Daniel
    Commented Aug 4, 2021 at 14:05
1504

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}

22
  • 14
    the || trick doesn't work if args[number] is 0. Should do an explicit if() to see if (args[number] === undefined).
    – fserb
    Commented Feb 19, 2011 at 14:16
  • 4
    in the else statement of the shorthand if, why not just do "match" instead of "'{' + number + '}'". match should equal that string.
    – mjc
    Commented Jul 10, 2011 at 4:40
  • 4
    If you have multiple strings appended to each other (with the +-operator), be sure to put the complete String in parentheses: ("asd {0}"+"fas {1}").format("first", "second"); Otherwise, the function will only be applied to the last string that was appended. Commented Oct 22, 2012 at 17:59
  • 3
    That slightly and subtly changes the outcome. Imagine 'foo {0}'.format(fnWithNoReturnValue()). It would currently return foo {0}. With your changes, it would return foo undefined.
    – fearphage
    Commented Feb 16, 2013 at 14:51
  • 4
    I think this is better than sprintf() for JS because it does basically the same thing and it is very small. Commented Sep 8, 2013 at 18:26
658

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

Firebug

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.

24
  • 548
    It's pretty cool to answer a question on stackoverflow with code from stackoverflow, +1
    – Sneakyness
    Commented Jan 18, 2014 at 0:02
  • 7
    @JamesManning The regex allows the global flag (g), which can replace the same key more than once. In the example above, you could use {name} multiple times in the same sentence and have them all replaced.
    – KrekkieD
    Commented Apr 24, 2014 at 13:00
  • 3
    This seems awfully fragile, to be honest. What happens for instance if name is "blah {adjective} blah"? Commented Jan 16, 2017 at 11:02
  • 8
    @ruffin “a little hyperbolic”? Code that is fooled into interpreting user data as format strings is an entire category of vulnerabilities. 98.44% is beyond mediocre. Commented Jun 5, 2017 at 16:17
  • 4
    @samhocevar I can't believe you Little Bobby Tabled me. ;) If you're running text processed by client-side JavaScript on your database server without any safety checks, heaven help us all. ;^) Look, there shouldn't be anything any user can send from a client (eg, Postman) that gets past your server's security. And you should assume anything dangerous that could be sent from a client will be. That is, if you require 100% safety from client-side JavaScript code which is always user editable, and you think this function could open a security risk, you're playing in the wrong game.
    – ruffin
    Commented Feb 19, 2018 at 18:57
360

Number Formatting in JavaScript

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:

Rounding floating-point numbers

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)

Exponential form

The equivalent of sprintf("%.2e", num) is num.toExponential(2).

(33333).toExponential(2); // "3.33e+4"

Hexadecimal and other bases

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

Reference Pages

Quick tutorial on JS number formatting

Mozilla reference page for toFixed() (with links to toPrecision(), toExponential(), toLocaleString(), ...)

7
  • 23
    Wouldn't it just be better to enclose the number literal in parenthesis, instead of leaving a weird white space there?
    – rmobis
    Commented Nov 26, 2012 at 1:53
  • 7
    That would probably look better, true. But my goal there is just to point out the syntax error trap.
    – rescdsk
    Commented Dec 1, 2012 at 21:28
  • 4
    Just a side note if you're using an older browser, or supporting older browsers, some browsers implemented toFixed incorrectly, using Math.round in place of toFixed is a better solution.
    – ars265
    Commented Feb 19, 2013 at 14:57
  • 7
    @Raphael_ and @rescdsk: .. also works: 33333..toExponential(2); Commented May 7, 2013 at 8:12
  • 1
    Relevant to this concern and technique is developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…. For example: String(4).padStart(5,'0') gives '00004'. Commented Oct 26, 2019 at 15:53
317

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.

10
  • 112
    The problem with template strings is that they seem to be executed immediately, making their use as, say, an i18n-like string table completely worthless. I can't define the string early on, and supply the parameters to use later and/or repeatedly.
    – Tustin2121
    Commented Dec 4, 2015 at 23:39
  • 5
    @Tustin2121 You're right that they're not built to be assigned to a variable, which is a bit mind-warping, but it's easy enough to work with templated strings' instant-execution tendancies if you hide them in a function. See jsfiddle.net/zvcm70pa Commented May 25, 2016 at 3:51
  • 17
    @Tustin2121 there is no difference between using a template string or old style string concatenation, its sugar for the same thing. You would have to wrap an old style string generator in a simple function and the same thing works fine with string templates. const compile = (x, y) => `I can call this template string whenever I want.. x=${x}, y=${y}` ... compile(30, 20) Commented May 27, 2016 at 6:22
  • 13
    this solution won't work for format string passed in variable (from server for example)
    – Mike
    Commented Nov 15, 2016 at 14:06
  • 9
    Template strings do not feature the formatting capabilities, hence why they're uncomparable in that regard. Ex. specifying argument width, or making sure that argument has exact precision.
    – Dragas
    Commented Apr 25, 2019 at 10:28
182

jsxt, Zippo

This option fits better.

String.prototype.format = function() {
    var formatted = this;
    for (var i = 0; i < arguments.length; i++) {
        var regexp = new RegExp('\\{'+i+'\\}', 'gi');
        formatted = formatted.replace(regexp, arguments[i]);
    }
    return formatted;
};

With this option I can replace strings like these:

'The {0} is dead. Don\'t code {0}. Code {1} that is open source!'.format('ASP', 'PHP');

With your code the second {0} wouldn't be replaced. ;)

3
  • 3
    gist.github.com/1049426 I updated your example with this approach. Numerous benefits including saving the native implementation if it exists, stringifying, etc. I tried removing regular expressions, but welp kind of needed for global replace. :-/
    – tbranyen
    Commented Jun 27, 2011 at 18:46
  • 7
    jsxt is GPL-licensed unfortunately
    – AndiDog
    Commented Jul 22, 2012 at 20:24
  • Very inefficient approach. Uses regex when not needed, looks up a whole string for searching many times. Commented Jan 23, 2022 at 6:48
130

For Node.js users there is util.format which has printf-like functionality:

util.format("%s world", "Hello")
4
  • 2
    This doesn't support %x as of Node v0.10.26
    – Max Krohn
    Commented Apr 24, 2014 at 13:16
  • 2
    Doesn't support width and alignment modifiers either (e.g. %-20s %5.2f)
    – FGM
    Commented Jan 4, 2019 at 13:29
  • 7
    I had to scroll all the way down the page to see this useful answer. Commented Apr 14, 2020 at 22:50
  • 1
    From util.format node documentation: "util.format() is a synchronous method that is intended as a debugging tool. Some input values can have a significant performance overhead that can block the event loop. Use this function with care and never in a hot code path."
    – r.pedrosa
    Commented Dec 15, 2022 at 16:28
120

I use this simple function:

String.prototype.format = function() {
    var formatted = this;
    for( var arg in arguments ) {
        formatted = formatted.replace("{" + arg + "}", arguments[arg]);
    }
    return formatted;
};

That's very similar to string.format:

"{0} is dead, but {1} is alive!".format("ASP", "ASP.NET")
11
  • 1
    why +=?, should it formatted = this.replace("{" + arg + "}", arguments[arg]); Commented Nov 30, 2010 at 6:30
  • 2
    I think the code is still not correct. The correct one one should be like Filipiz posted.
    – wenqiang
    Commented Jan 11, 2011 at 2:48
  • 3
    For reference, for...in won't work in every browser as this code expects it to. It'll loop over all enumerable properties, which in some browsers will include arguments.length, and in others won't even include the arguments themselves at all. In any case, if Object.prototype is added to, any additions will probably be included in the bunch. The code should be using a standard for loop, rather than for...in.
    – cHao
    Commented Feb 6, 2011 at 21:10
  • 3
    This fails if a previous replacement contains a format string as well: "{0} is dead, but {1} is alive!".format("{1}", "ASP.NET") === "ASP.NET is dead, but ASP.NET is alive!"
    – Gumbo
    Commented Mar 28, 2011 at 14:05
  • 6
    The variable arg is global. You need to do this instead: for (var arg in arguments) {
    – Pauan
    Commented Dec 5, 2012 at 22:49
69

I'm surprised no one used reduce, this is a native concise and powerful JavaScript function.

ES6 (EcmaScript2015)

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

< ES6

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

4
  • 5
    Here's a version that uses this approach to create a simplified printf function: jsfiddle.net/11szrbx9 Commented Jul 20, 2015 at 22:49
  • 1
    And here is another one using ES6, in one line: (...a) => {return a.reduce((p: string, c: any) => p.replace(/%s/, c));
    – dtasev
    Commented Feb 22, 2018 at 11:59
  • 1
    No need for String.prototype.format in ES6: ((a,b,c)=>`${a}, ${b} and ${c}`)(...['me', 'myself', 'I']) (note that this is a bit redundant to better fit in your example)
    – Tino
    Commented Apr 5, 2018 at 21:50
  • You'd have to implement replacement functions for each of printf's type specifiers and include logic for padding prefixes. Iterating over the format string in a sensible fashion seems to be the minor challenge here, imho. Neat solution if you only need string replacements, though.
    – collapsar
    Commented Jul 6, 2018 at 10:19
55

Here's a minimal implementation of sprintf in JavaScript: it only does "%s" and "%d", but I have left space for it to be extended. It is useless to the OP, but other people who stumble across this thread coming from Google might benefit from it.

function sprintf() {
    var args = arguments,
    string = args[0],
    i = 1;
    return string.replace(/%((%)|s|d)/g, function (m) {
        // m is the matched format, e.g. %s, %d
        var val = null;
        if (m[2]) {
            val = m[2];
        } else {
            val = args[i];
            // A switch statement so that the formatter can be extended. Default is %s
            switch (m) {
                case '%d':
                    val = parseFloat(val);
                    if (isNaN(val)) {
                        val = 0;
                    }
                    break;
            }
            i++;
        }
        return val;
    });
}

Example:

alert(sprintf('Latitude: %s, Longitude: %s, Count: %d', 41.847, -87.661, 'two'));
// Expected output: Latitude: 41.847, Longitude: -87.661, Count: 0

In contrast with similar solutions in previous replies, this one does all substitutions in one go, so it will not replace parts of previously replaced values.

1
  • any idea how to suit for %02d? Commented Sep 10, 2021 at 2:25
34

3 different ways to format javascript string

There are 3 different ways to format a string by replacing placeholders with the variable value.

  1. Using template literal (backticks ``)

    let name = 'John';
    let age = 30;
    // using backticks
    console.log(`${name} is ${age} years old.`);
    // John is 30 years old.

  2. Using concatenation

let name = 'John';
let age = 30;
// using concatenation
console.log(name + ' is ' + age + ' years old.');
// John is 30 years old.

  1. Creating own format function

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

3
  • 2
    Thanks! 3rd option suits for my case. Commented Nov 19, 2021 at 7:05
  • 1
    3rd option is the only one that allows variables to be "injected" AFTER declaring the string.
    – Prid
    Commented Jun 29, 2022 at 17:39
  • The third option is called "monkey patching" and this is generally considered an antipattern, generally only to be used when backporting a feature (like Array.prototype.forEach) to an older engine. See the "Warning" in article on MDN. While not a completely symmetric with printf, util.format is probably the best choice for what the op is looking for.
    – Subfuzion
    Commented Aug 29, 2022 at 6:58
32

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());
0
28

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'
1
28

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

ES 2015 update

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! %%"
2
  • 3
    this answer was great for a quick copy paste into an existing function. No require no downloads etc.
    – Nick
    Commented Jun 24, 2017 at 20:41
  • The only answer that can satisfy. IDK why people suggesting template literals. printf/sprintf doesn't limit the arguments, but when using template literals, I have to know the variables and those are not fixed. Common sense is rarely common! sorry for being rude! 😑
    – ssi-anik
    Commented Jul 11, 2023 at 13:55
20

+1 Zippo with the exception that the function body needs to be as below or otherwise it appends the current string on every iteration:

String.prototype.format = function() {
    var formatted = this;
    for (var arg in arguments) {
        formatted = formatted.replace("{" + arg + "}", arguments[arg]);
    }
    return formatted;
};
4
  • 1
    It didn't work on Firefox. The debugger show arg as undefined.
    – xiao 啸
    Commented Dec 11, 2010 at 10:27
  • It does not replace the second character 'The {0} is dead. Don\'t code {0}. Code {1} that is open source!'.format('ASP', 'PHP'); the result becomes The ASP is dead. Don't code {0}. Code PHP that is open source!. One more thing for(arg in arguments) does not work in IE. i replaced with for (arg = 0; arg <arguments.length; arg++) Commented Jan 28, 2011 at 1:52
  • 2
    For reference, for...in won't work in every browser as this code expects it to. It'll loop over all enumerable properties, which in some browsers will include arguments.length, and in others won't even include the arguments themselves at all. In any case, if Object.prototype is added to, any additions will probably be included in the bunch. The code should be using a standard for loop, rather than for...in.
    – cHao
    Commented Feb 6, 2011 at 21:12
  • You should propose an answer edit instead of duplicate answer. This duplicate this answer Commented May 9, 2019 at 9:16
15

I use a small library called String.format for JavaScript which supports most of the format string capabilities (including format of numbers and dates), and uses the .NET syntax. The script itself is smaller than 4 kB, so it doesn't create much of overhead.

5
  • I took a look at that library and it looks really great. I was pissed off when I saw that the download was an EXE. What the heck is that about? Didn't download.
    – jessegavin
    Commented Apr 6, 2010 at 18:04
  • Often a downloadable archive that's an EXE is nothing more than a "self-extracting ZIP". Execute it, and it will unpack itself. This is quite convenient BUT because it looks so much like malware, the format is not used on the web all that often any more. Commented Jul 1, 2013 at 7:46
  • While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes.
    – starmole
    Commented Jun 24, 2014 at 4:56
  • @starmole the link is to a (minified) 4 kB javascript library. I don't believe pasting it into the answer is a good idea.
    – ivarni
    Commented Jun 24, 2014 at 5:07
  • You are right pasting it would be no better. I just got this comment for random review - and commented before disliking it. To me stackoverflow is better when providing explanations rather than ready made solutions (which the link is). I also do not want to encourage people to post or download black-box code.
    – starmole
    Commented Jun 24, 2014 at 5:16
14

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.

0
13

If you are looking to handle the thousands separator, you should really use toLocaleString() from the JavaScript Number class since it will format the string for the user's region.

The JavaScript Date class can format localized dates and times.

2
  • 1
    It's actually a set by the user as a setting in the application (not the machine their on) but I'll take a look, thanks
    – Chris S
    Commented Mar 4, 2009 at 13:19
  • add some examples so that everyone can understands it quickly. Commented Jun 24, 2014 at 4:57
13

Very elegant:

String.prototype.format = function (){
    var args = arguments;
    return this.replace(/\{\{|\}\}|\{(\d+)\}/g, function (curlyBrack, index) {
        return ((curlyBrack == "{{") ? "{" : ((curlyBrack == "}}") ? "}" : args[index]));
    });
};

// Usage:
"{0}{1}".format("{1}", "{0}")

Credit goes to (broken link) https://gist.github.com/0i0/1519811

1
  • This is the only one that handles escape brackets {{0}} as well as things like {0}{1}.format("{1}", "{0}"). Should be at the very top!
    – Juan
    Commented Jul 9, 2014 at 6:54
10

The PHPJS project has written JavaScript implementations for many of PHP's functions. Since PHP's sprintf() function is basically the same as C's printf(), their JavaScript implementation of it should satisfy your needs.

9

I use this one:

String.prototype.format = function() {
    var newStr = this, i = 0;
    while (/%s/.test(newStr))
        newStr = newStr.replace("%s", arguments[i++])

    return newStr;
}

Then I call it:

"<h1>%s</h1><p>%s</p>".format("Header", "Just a test!");
0
9

I have a solution very close to Peter's, but it deals with number and object case.

if (!String.prototype.format) {
  String.prototype.format = function() {
    var args;
    args = arguments;
    if (args.length === 1 && args[0] !== null && typeof args[0] === 'object') {
      args = args[0];
    }
    return this.replace(/{([^}]*)}/g, function(match, key) {
      return (typeof args[key] !== "undefined" ? args[key] : match);
    });
  };
}

Maybe it could be even better to deal with the all deeps cases, but for my needs this is just fine.

"This is an example from {name}".format({name:"Blaine"});
"This is an example from {0}".format("Blaine");

PS: This function is very cool if you are using translations in templates frameworks like AngularJS:

<h1> {{('hello-message'|translate).format(user)}} <h1>
<h1> {{('hello-by-name'|translate).format( user ? user.name : 'You' )}} <h1>

Where the en.json is something like

{
    "hello-message": "Hello {name}, welcome.",
    "hello-by-name": "Hello {0}, welcome."
}
1
  • the [^}] part in the regexp is unnecesary.. use {(.*?)} instead, or better {([\s\S]*?)} to match newline too.
    – rawiro
    Commented Oct 1, 2014 at 8:26
8

One very slightly different version, the one I prefer (this one uses {xxx} tokens rather than {0} numbered arguments, this is much more self-documenting and suits localization much better):

String.prototype.format = function(tokens) {
  var formatted = this;
  for (var token in tokens)
    if (tokens.hasOwnProperty(token))
      formatted = formatted.replace(RegExp("{" + token + "}", "g"), tokens[token]);
  return formatted;
};

A variation would be:

  var formatted = l(this);

that calls an l() localization function first.

6

For basic formatting:

var template = jQuery.validator.format("{0} is not a valid value");
var result = template("abc");
6

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
0
5

Just in case someone needs a function to prevent polluting global scope, here is the function that does the same:

  function _format (str, arr) {
    return str.replace(/{(\d+)}/g, function (match, number) {
      return typeof arr[number] != 'undefined' ? arr[number] : match;
    });
  };
5

For those who like Node.JS and its util.format feature, I've just extracted it out into its vanilla JavaScript form (with only functions that util.format uses):

exports = {};

function isString(arg) {
    return typeof arg === 'string';
}
function isNull(arg) {
    return arg === null;
}
function isObject(arg) {
    return typeof arg === 'object' && arg !== null;
}
function isBoolean(arg) {
    return typeof arg === 'boolean';
}
function isUndefined(arg) {
    return arg === void 0;
}
function stylizeNoColor(str, styleType) {
    return str;
}
function stylizeWithColor(str, styleType) {
    var style = inspect.styles[styleType];

    if (style) {
        return '\u001b[' + inspect.colors[style][0] + 'm' + str +
            '\u001b[' + inspect.colors[style][3] + 'm';
    } else {
        return str;
    }
}
function isFunction(arg) {
    return typeof arg === 'function';
}
function isNumber(arg) {
    return typeof arg === 'number';
}
function isSymbol(arg) {
    return typeof arg === 'symbol';
}
function formatPrimitive(ctx, value) {
    if (isUndefined(value))
        return ctx.stylize('undefined', 'undefined');
    if (isString(value)) {
        var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '')
                .replace(/'/g, "\\'")
                .replace(/\\"/g, '"') + '\'';
        return ctx.stylize(simple, 'string');
    }
    if (isNumber(value)) {
        // Format -0 as '-0'. Strict equality won't distinguish 0 from -0,
        // so instead we use the fact that 1 / -0 < 0 whereas 1 / 0 > 0 .
        if (value === 0 && 1 / value < 0)
            return ctx.stylize('-0', 'number');
        return ctx.stylize('' + value, 'number');
    }
    if (isBoolean(value))
        return ctx.stylize('' + value, 'boolean');
    // For some reason typeof null is "object", so special case here.
    if (isNull(value))
        return ctx.stylize('null', 'null');
    // es6 symbol primitive
    if (isSymbol(value))
        return ctx.stylize(value.toString(), 'symbol');
}
function arrayToHash(array) {
    var hash = {};

    array.forEach(function (val, idx) {
        hash[val] = true;
    });

    return hash;
}
function objectToString(o) {
    return Object.prototype.toString.call(o);
}
function isDate(d) {
    return isObject(d) && objectToString(d) === '[object Date]';
}
function isError(e) {
    return isObject(e) &&
        (objectToString(e) === '[object Error]' || e instanceof Error);
}
function isRegExp(re) {
    return isObject(re) && objectToString(re) === '[object RegExp]';
}
function formatError(value) {
    return '[' + Error.prototype.toString.call(value) + ']';
}
function formatPrimitiveNoColor(ctx, value) {
    var stylize = ctx.stylize;
    ctx.stylize = stylizeNoColor;
    var str = formatPrimitive(ctx, value);
    ctx.stylize = stylize;
    return str;
}
function isArray(ar) {
    return Array.isArray(ar);
}
function hasOwnProperty(obj, prop) {
    return Object.prototype.hasOwnProperty.call(obj, prop);
}
function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) {
    var name, str, desc;
    desc = Object.getOwnPropertyDescriptor(value, key) || {value: value[key]};
    if (desc.get) {
        if (desc.set) {
            str = ctx.stylize('[Getter/Setter]', 'special');
        } else {
            str = ctx.stylize('[Getter]', 'special');
        }
    } else {
        if (desc.set) {
            str = ctx.stylize('[Setter]', 'special');
        }
    }
    if (!hasOwnProperty(visibleKeys, key)) {
        name = '[' + key + ']';
    }
    if (!str) {
        if (ctx.seen.indexOf(desc.value) < 0) {
            if (isNull(recurseTimes)) {
                str = formatValue(ctx, desc.value, null);
            } else {
                str = formatValue(ctx, desc.value, recurseTimes - 1);
            }
            if (str.indexOf('\n') > -1) {
                if (array) {
                    str = str.split('\n').map(function (line) {
                        return '  ' + line;
                    }).join('\n').substr(2);
                } else {
                    str = '\n' + str.split('\n').map(function (line) {
                        return '   ' + line;
                    }).join('\n');
                }
            }
        } else {
            str = ctx.stylize('[Circular]', 'special');
        }
    }
    if (isUndefined(name)) {
        if (array && key.match(/^\d+$/)) {
            return str;
        }
        name = JSON.stringify('' + key);
        if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) {
            name = name.substr(1, name.length - 2);
            name = ctx.stylize(name, 'name');
        } else {
            name = name.replace(/'/g, "\\'")
                .replace(/\\"/g, '"')
                .replace(/(^"|"$)/g, "'")
                .replace(/\\\\/g, '\\');
            name = ctx.stylize(name, 'string');
        }
    }

    return name + ': ' + str;
}
function formatArray(ctx, value, recurseTimes, visibleKeys, keys) {
    var output = [];
    for (var i = 0, l = value.length; i < l; ++i) {
        if (hasOwnProperty(value, String(i))) {
            output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
                String(i), true));
        } else {
            output.push('');
        }
    }
    keys.forEach(function (key) {
        if (!key.match(/^\d+$/)) {
            output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
                key, true));
        }
    });
    return output;
}
function reduceToSingleString(output, base, braces) {
    var length = output.reduce(function (prev, cur) {
        return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1;
    }, 0);

    if (length > 60) {
        return braces[0] +
            (base === '' ? '' : base + '\n ') +
            ' ' +
            output.join(',\n  ') +
            ' ' +
            braces[1];
    }

    return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1];
}
function formatValue(ctx, value, recurseTimes) {
    // Provide a hook for user-specified inspect functions.
    // Check that value is an object with an inspect function on it
    if (ctx.customInspect &&
        value &&
        isFunction(value.inspect) &&
            // Filter out the util module, it's inspect function is special
        value.inspect !== exports.inspect &&
            // Also filter out any prototype objects using the circular check.
        !(value.constructor && value.constructor.prototype === value)) {
        var ret = value.inspect(recurseTimes, ctx);
        if (!isString(ret)) {
            ret = formatValue(ctx, ret, recurseTimes);
        }
        return ret;
    }

    // Primitive types cannot have properties
    var primitive = formatPrimitive(ctx, value);
    if (primitive) {
        return primitive;
    }

    // Look up the keys of the object.
    var keys = Object.keys(value);
    var visibleKeys = arrayToHash(keys);

    if (ctx.showHidden) {
        keys = Object.getOwnPropertyNames(value);
    }

    // This could be a boxed primitive (new String(), etc.), check valueOf()
    // NOTE: Avoid calling `valueOf` on `Date` instance because it will return
    // a number which, when object has some additional user-stored `keys`,
    // will be printed out.
    var formatted;
    var raw = value;
    try {
        // the .valueOf() call can fail for a multitude of reasons
        if (!isDate(value))
            raw = value.valueOf();
    } catch (e) {
        // ignore...
    }

    if (isString(raw)) {
        // for boxed Strings, we have to remove the 0-n indexed entries,
        // since they just noisey up the output and are redundant
        keys = keys.filter(function (key) {
            return !(key >= 0 && key < raw.length);
        });
    }

    // Some type of object without properties can be shortcutted.
    if (keys.length === 0) {
        if (isFunction(value)) {
            var name = value.name ? ': ' + value.name : '';
            return ctx.stylize('[Function' + name + ']', 'special');
        }
        if (isRegExp(value)) {
            return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
        }
        if (isDate(value)) {
            return ctx.stylize(Date.prototype.toString.call(value), 'date');
        }
        if (isError(value)) {
            return formatError(value);
        }
        // now check the `raw` value to handle boxed primitives
        if (isString(raw)) {
            formatted = formatPrimitiveNoColor(ctx, raw);
            return ctx.stylize('[String: ' + formatted + ']', 'string');
        }
        if (isNumber(raw)) {
            formatted = formatPrimitiveNoColor(ctx, raw);
            return ctx.stylize('[Number: ' + formatted + ']', 'number');
        }
        if (isBoolean(raw)) {
            formatted = formatPrimitiveNoColor(ctx, raw);
            return ctx.stylize('[Boolean: ' + formatted + ']', 'boolean');
        }
    }

    var base = '', array = false, braces = ['{', '}'];

    // Make Array say that they are Array
    if (isArray(value)) {
        array = true;
        braces = ['[', ']'];
    }

    // Make functions say that they are functions
    if (isFunction(value)) {
        var n = value.name ? ': ' + value.name : '';
        base = ' [Function' + n + ']';
    }

    // Make RegExps say that they are RegExps
    if (isRegExp(value)) {
        base = ' ' + RegExp.prototype.toString.call(value);
    }

    // Make dates with properties first say the date
    if (isDate(value)) {
        base = ' ' + Date.prototype.toUTCString.call(value);
    }

    // Make error with message first say the error
    if (isError(value)) {
        base = ' ' + formatError(value);
    }

    // Make boxed primitive Strings look like such
    if (isString(raw)) {
        formatted = formatPrimitiveNoColor(ctx, raw);
        base = ' ' + '[String: ' + formatted + ']';
    }

    // Make boxed primitive Numbers look like such
    if (isNumber(raw)) {
        formatted = formatPrimitiveNoColor(ctx, raw);
        base = ' ' + '[Number: ' + formatted + ']';
    }

    // Make boxed primitive Booleans look like such
    if (isBoolean(raw)) {
        formatted = formatPrimitiveNoColor(ctx, raw);
        base = ' ' + '[Boolean: ' + formatted + ']';
    }

    if (keys.length === 0 && (!array || value.length === 0)) {
        return braces[0] + base + braces[1];
    }

    if (recurseTimes < 0) {
        if (isRegExp(value)) {
            return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
        } else {
            return ctx.stylize('[Object]', 'special');
        }
    }

    ctx.seen.push(value);

    var output;
    if (array) {
        output = formatArray(ctx, value, recurseTimes, visibleKeys, keys);
    } else {
        output = keys.map(function (key) {
            return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array);
        });
    }

    ctx.seen.pop();

    return reduceToSingleString(output, base, braces);
}
function inspect(obj, opts) {
    // default options
    var ctx = {
        seen: [],
        stylize: stylizeNoColor
    };
    // legacy...
    if (arguments.length >= 3) ctx.depth = arguments[2];
    if (arguments.length >= 4) ctx.colors = arguments[3];
    if (isBoolean(opts)) {
        // legacy...
        ctx.showHidden = opts;
    } else if (opts) {
        // got an "options" object
        exports._extend(ctx, opts);
    }
    // set default options
    if (isUndefined(ctx.showHidden)) ctx.showHidden = false;
    if (isUndefined(ctx.depth)) ctx.depth = 2;
    if (isUndefined(ctx.colors)) ctx.colors = false;
    if (isUndefined(ctx.customInspect)) ctx.customInspect = true;
    if (ctx.colors) ctx.stylize = stylizeWithColor;
    return formatValue(ctx, obj, ctx.depth);
}
exports.inspect = inspect;


// http://en.wikipedia.org/wiki/ANSI_escape_code#graphics
inspect.colors = {
    'bold': [1, 22],
    'italic': [3, 23],
    'underline': [4, 24],
    'inverse': [7, 27],
    'white': [37, 39],
    'grey': [90, 39],
    'black': [30, 39],
    'blue': [34, 39],
    'cyan': [36, 39],
    'green': [32, 39],
    'magenta': [35, 39],
    'red': [31, 39],
    'yellow': [33, 39]
};

// Don't use 'blue' not visible on cmd.exe
inspect.styles = {
    'special': 'cyan',
    'number': 'yellow',
    'boolean': 'yellow',
    'undefined': 'grey',
    'null': 'bold',
    'string': 'green',
    'symbol': 'green',
    'date': 'magenta',
    // "name": intentionally not styling
    'regexp': 'red'
};


var formatRegExp = /%[sdj%]/g;
exports.format = function (f) {
    if (!isString(f)) {
        var objects = [];
        for (var j = 0; j < arguments.length; j++) {
            objects.push(inspect(arguments[j]));
        }
        return objects.join(' ');
    }

    var i = 1;
    var args = arguments;
    var len = args.length;
    var str = String(f).replace(formatRegExp, function (x) {
        if (x === '%%') return '%';
        if (i >= len) return x;
        switch (x) {
            case '%s':
                return String(args[i++]);
            case '%d':
                return Number(args[i++]);
            case '%j':
                try {
                    return JSON.stringify(args[i++]);
                } catch (_) {
                    return '[Circular]';
                }
            default:
                return x;
        }
    });
    for (var x = args[i]; i < len; x = args[++i]) {
        if (isNull(x) || !isObject(x)) {
            str += ' ' + x;
        } else {
            str += ' ' + inspect(x);
        }
    }
    return str;
};

Harvested from: https://github.com/joyent/node/blob/master/lib/util.js

5

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!
4

I have a slightly longer formatter for JavaScript here...

You can do formatting several ways:

  • String.format(input, args0, arg1, ...)
  • String.format(input, obj)
  • "literal".format(arg0, arg1, ...)
  • "literal".format(obj)

Also, if you have say a ObjectBase.prototype.format (such as with DateJS) it will use that.

Examples...

var input = "numbered args ({0}-{1}-{2}-{3})";
console.log(String.format(input, "first", 2, new Date()));
//Outputs "numbered args (first-2-Thu May 31 2012...Time)-{3})"

console.log(input.format("first", 2, new Date()));
//Outputs "numbered args(first-2-Thu May 31 2012...Time)-{3})"

console.log(input.format(
    "object properties ({first}-{second}-{third:yyyy-MM-dd}-{fourth})"
    ,{
        'first':'first'
        ,'second':2
        ,'third':new Date() //assumes Date.prototype.format method
    }
));
//Outputs "object properties (first-2-2012-05-31-{3})"

I've also aliased with .asFormat and have some detection in place in case there's already a string.format (such as with MS Ajax Toolkit (I hate that library).

Not the answer you're looking for? Browse other questions tagged or ask your own question.