2401

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.

Edward Brey
  • 40,302
  • 20
  • 199
  • 253
Chris S
  • 64,770
  • 52
  • 221
  • 239
  • 4
    Aside all the great answers below, you may want to take a look at this one: http://stackoverflow.com/a/2648463/1712065 which IMO, is the most efficient solution to this problem. – Annie Apr 24 '15 at 02:54
  • 3
    I wrote a [cheap one](https://jsfiddle.net/B1KMusic/dx52ywo4/) that uses C-like printf syntax. – Braden Best Jan 28 '16 at 19:38
  • var search = [$scope.dog, "1"]; var url = vsprintf("http://earth/Services/dogSearch.svc/FindMe/%s/%s", search); ***For node, you can get your module by "npm install sprintf-js" – Jenna Leaf Apr 29 '16 at 19:25
  • I have also written a simple function to achieve this; https://stackoverflow.com/a/54345052/5927126 – AnandShanbhag Jan 24 '19 at 11:10
  • I have since released a fast and spec compliant printf implementation for Node.js and browser https://github.com/gajus/fast-printf – Gajus Jan 03 '21 at 02:53
  • 4
    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 Jan 23 '21 at 23:01
  • Besides `template string`; `String.padStart` might be the other thing ppl looking for. (See https://stackoverflow.com/questions/2686855/is-there-a-javascript-function-that-can-pad-a-string-to-get-to-a-determined-leng ) – Nor.Z Feb 19 '23 at 12:39

61 Answers61

1611

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.

AMJ
  • 125
  • 1
  • 6
Gumbo
  • 643,351
  • 109
  • 780
  • 844
  • 28
    If only some simple number-to-string conversion is desired, `num.toFixed()` method might be enough! – heltonbiker Dec 17 '12 at 23:29
  • 2
    @MaksymilianMajer that seems to be something massively different. – Evan Carroll Apr 14 '14 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. – Maksymilian Majer Apr 15 '14 at 07:41
  • @MaksymilianMajer right, just saying this answer is dead, and the link has decayed. It needs to be totally purged. – Evan Carroll Apr 15 '14 at 07:43
  • @Evan the link is fine and goes through. – Octavia Togami May 15 '14 at 03:50
  • A simple perf test, its more elegant to use ''.format but it has poor performance; use when necessary. http://jsperf.com/string-format-mjames – mike james Aug 18 '14 at 12:29
  • I wrote a near-perfect replica of PHP's sprintf function for JavaScript, please enjoy: https://github.com/Alhadis/Snippets/blob/master/js/str-sprintf.js ... please bear in mind it DOES extend the String prototype (e.g., invoke it by "%f".sprintf(0.5), etc). It has zero external dependencies and is pretty damn fast too. Only discrepancies from PHP's implementation would be more lenient error handling. :-) –  Feb 16 '15 at 01:52
  • fearphage's suggestion now handles this test correctly. – Dude Pascalou Dec 31 '15 at 13:18
  • I think the best is _.template function from lodash or underscore. – HugoPoi Aug 03 '16 at 13:07
  • 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. – Ryan Shillington Jul 02 '18 at 19:17
  • @RyanShillington Unless you're not transpiling and are still supporting Internet Explorer (of any version), which some of us still are `:(`. [**Template strings are not supported by any version of IE** and likely never will be](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#browser_compatibility). Remember that your html files in many templating engines aren't transpiled. I see lots of es6 still in inline code from folks who think es6 is ubiquitous. It's not... quite yet. – ruffin Jan 26 '21 at 13:40
  • 6
    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 Aug 04 '21 at 14:05
  • This wouldn't work with constants. – hrzafer Feb 17 '22 at 22:24
  • If you would want to use more complex `sprintf()` placeholders such as `%-5.2f`, you can either use the above linked `sprintf()` JS implementation or use it's source as a cheat sheet to figure correct functions to call within the ES6 template syntax: https://github.com/alexei/sprintf.js/blob/master/src/sprintf.js#L69 – Mikko Rantalainen Dec 09 '22 at 13:25
1497

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}

Adriaan
  • 17,741
  • 7
  • 42
  • 75
fearphage
  • 16,808
  • 1
  • 27
  • 33
  • 14
    the || trick doesn't work if args[number] is 0. Should do an explicit if() to see if (args[number] === undefined). – fserb Feb 19 '11 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. – mikeycgto Jul 10 '11 at 04: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. – Lukas Knuth Oct 22 '12 at 17:59
  • Great little function. The typeof check could be replaced with `return number in args` to make it even shorter. – Daan Mortier Feb 15 '13 at 22:27
  • 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 Feb 16 '13 at 14:51
  • Your proposed solution suffers from the same things as some of the others here. `'{0}{1}'.format('{1}', '{0}')` should return `{1}{0}`. Also yours seems to only be faster in Firefox. – fearphage May 17 '13 at 03:16
  • Is there a way to make JSHint happy with the regex? "JSHint: Unescaped '{'." – avenmore Jun 21 '13 at 06:56
  • 4
    I think this is better than `sprintf()` for JS because it does basically the same thing and it is very small. – user2233706 Sep 08 '13 at 18:26
  • 1
    More benchmarks, building on what @RandomEngy furnished, and snippets from other answers to this question: http://jsperf.com/stringformat/6 – fish2000 Dec 02 '13 at 21:07
  • How about something like `String.format('function() {{ {0} }}', fnBody);`? Currently, the result would still have the double braces, but what if I want to replace those with a single one? That would be more concise with the .NET version. Really like your solution though! Nice and small! :) – Boris Jan 28 '14 at 17:35
  • Of course, a better example would be `String.format('{{0}} was replaced with {0}', replcmnt);`. – Boris Jan 28 '14 at 17:40
  • 1
    Note that `String.format("{{0}}", "a")` will give you `"{a}"` instead of `"{0}"`, as the .NET version would. Tested several codes here and found one that works [way down the list](http://stackoverflow.com/a/8463429/1219414). – Juan Jul 09 '14 at 07:09
  • 1
    I know this is old, but I just got to it. Love this function! Thanks! However, I just ran into a problem where is a replacement value is null, it will put the word `null` as the value. Is this intended behavior? If not, can it be fixed/modified? – RoLYroLLs Dec 05 '14 at 00:14
  • 1
    @RoLYroLLs You can change the internal check to be less specific. Replace `typeof args[number] != 'undefined'` with `args[number] != null`. This will match both `null` and `undefined` values. If you never planned to pass in falsey values (0, empty string, boolean false, etc) you could remove ternary altogether and make it `return args[number] || match`. – fearphage Dec 08 '14 at 18:54
  • 1
    I really like this and used it, but changed the If statement conditional check to be strict `if (typeof String.prototype.format === "undefined") {`, it is a personal preference. I also escaped the curly braces `return this.replace(/\{(\d+)\}/g, function (match, number) {` – beenhere4hours Mar 25 '15 at 13:03
  • @dmasi Those are both valid suggestions for a much more strict environment. I won't be incorporating those, but I appreciate the suggestion. – fearphage Mar 25 '15 at 21:28
  • this version works with objects too: function format(str, args) { if (arguments.length > 2) args = Array.prototype.slice.call(arguments, 1); if (typeof args !== 'object') args = [ args ]; return str.replace(/{(.*?)}/g, function(match, item) { return typeof args[item] != 'undefined' ? args[item] : match; }); }; – Bernardo Ramos Nov 03 '15 at 05:19
  • Dont't use this solution in performance oriented code blocks. It is terribly slow. http://jsperf.com/string-format-vs-string-concatenation – aleha_84 Mar 13 '16 at 11:28
  • 2
    Thanks for a great solution. I would add that for readability it might be worth using \w+ instead of \d+, and args = arguments[0]. This way you can have .format({'count': count, 'page': page, etc.}). – user420667 Mar 14 '16 at 19:49
  • @fearphage : Probably a silly question, but shouldn't `args` be inaccessible within the scope of the anonymous function being passed to `String.replace()`? Would an arrow function not be required instead? – sookie Sep 26 '17 at 13:33
  • The most recent version of chrome is not going through this code so whenever i try to do format i'm getting format is not a function, So i warped the code inside a function and i'm calling it before calling format! – Samy Massoud Jan 08 '18 at 09:42
  • 1
    I think in Typescript, the signature could be change to this: `String.format = function(format:string, args: string[]): string {`. Then the `Array.prototype` line could be removed. Just a thought – Ehtesh Choudhury Mar 08 '19 at 08:43
640

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.

ruffin
  • 16,507
  • 9
  • 88
  • 138
Gabriel Nahmias
  • 920
  • 3
  • 15
  • 20
  • 535
    It's pretty cool to answer a question on stackoverflow with code from stackoverflow, +1 – Sneakyness Jan 18 '14 at 00:02
  • 1
    Anyone understand why they're using a regex for the replace instead of just a string? – James Manning Apr 13 '14 at 16:51
  • 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 Apr 24 '14 at 13:00
  • I think there is a typo: `var args = typeof arguments[0]` should be `var arg = typeof arguments[0]` – RainChen Jun 02 '15 at 08:25
  • @RainChen there is no typo. He are just reusing the same variable to store the typeof and then, in the next line, the arguments. – Dinei Aug 13 '15 at 14:53
  • 2
    @DineiA.Rockenbach but the code `for (arg in args)` will define a global variable named `arg`. If you're not using `var arg` at the first line, then you should use `for(var arg in args)` to avoid defining the global variable. – RainChen Aug 22 '15 at 14:17
  • @DineiA.Rockenbach here is a demo http://jsbin.com/keluvukuka/edit?html,console,output – RainChen Aug 22 '15 at 14:25
  • 1
    This behaves weird if `args[arg]` contains `$1`, `$'` etc. The solution is to replace `args[arg]` with `function() { return args[arg]; }`. – rr- Apr 10 '16 at 08:22
  • This would be cooler if you could just put javascript expressions inside the curly braces and dispense with the names. c# 6.0 has this now. – toddmo Sep 07 '16 at 18:33
  • This method is much better than something like `sprintf`, since the named placeholders allow ordering. This is important if you outsource the initial string e. g. for localization. Imagine you have something like `sprintf("Hello %s %s", ..., ...)`. In one language you want to place the forename first and then the surname, but in other language the surname first. The missing formatting is a disadvantage but one can still format the value in the params. – StanE Dec 23 '16 at 03:10
  • 3
    This seems awfully fragile, to be honest. What happens for instance if `name` is `"blah {adjective} blah"`? – sam hocevar Jan 16 '17 at 11:02
  • 2
    @samhocevar Um, `"Hello, blah OK blah, are you feeling OK?"`, natch. "Awfully" seems a little hyperbolic. If you wanted to [code to ignore `{` in replacement values, you could easily](https://pastebin.com/raw/Eb7QMn3e) or even make two passes if you want the more troublesome `adjective = "blah {name} blah"` to execute -- but boy, it does 98.44% of the job well as is, I believe. – ruffin Jun 05 '17 at 15:01
  • 7
    @ruffin “a little hyperbolic”? Code that is fooled into interpreting user data as format strings is an entire [category of vulnerabilities](https://en.wikipedia.org/wiki/Uncontrolled_format_string). 98.44% is *beyond mediocre*. – sam hocevar Jun 05 '17 at 16:17
  • 1
    @samhocevar By assuming "user data", you've made this a much different conversation, one I don't see implied by the OP or this answer. As you're presenting, you might as well throw out `String.replace` too. ;^) I've given code that stops circular refs. Do you think SO is vulnerable somehow for using `formatUnicorn`? Or I'm missing your point entirely -- maybe you could submit an answer that improves on this one? (Btw, 99.44% (sorry for 98 typo earlier) is just a [colloquialism](http://tse3.mm.bing.net/th?id=OIP.L-OVC63A98jkegBOyxLkFAEsDh&pid=15.1)) – ruffin Jun 05 '17 at 17:53
  • 1
    @ruffin yes, stuff fed to `{name}` is [typically user data](https://xkcd.com/327/). I do not know whether SO is vulnerable, but I do know they use a buggy function. (sadly, I came here looking for available options for string interpolation in JS and am thus unable to provide an improvement, but rest assured I would have had I been more literate in that language) – sam hocevar Jun 06 '17 at 09:16
  • 3
    "If I had ever learnt, I should have been a great proficient." - Lady Catherine de Bourgh. :-) – mwardm Nov 08 '17 at 15:45
  • 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 Feb 19 '18 at 18:57
  • Too bad `ReferenceError: formatUnicorn is not defined` – A1rPun May 16 '19 at 12:19
  • Why can't we use 'arguments.slice()' rather than Array.prototype.slice.call(arguments)? – Sarath S Menon Feb 27 '20 at 08:13
  • @SarathSMenon Check [this question](https://stackoverflow.com/questions/960866/how-can-i-convert-the-arguments-object-to-an-array-in-javascript) or the explanation of "Array-like" [here at MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments#description). – ruffin Jan 21 '21 at 15:10
  • Wow, this is _exactly_ what is needed to format PHP PS3 logs client side using the `message` and `context` properties. Awesome. https://www.php-fig.org/psr/psr-3/ – vctls Jun 11 '21 at 09:02
  • I believe this a npm package [format-unicorn](https://www.npmjs.com/package/format-unicorn). – Ibrahim.H Sep 21 '22 at 21:09
  • Try `"{{good}".formatUnicorn({"good":"bad}","bad":"good"})` – Meow Jan 04 '23 at 14:53
352

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(), ...)

rescdsk
  • 8,739
  • 4
  • 36
  • 32
  • 23
    Wouldn't it just be better to enclose the number literal in parenthesis, instead of leaving a weird white space there? – rmobis Nov 26 '12 at 01:53
  • 7
    That would probably look better, true. But my goal there is just to point out the syntax error trap. – rescdsk Dec 01 '12 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 Feb 19 '13 at 14:57
  • 7
    @Raphael_ and @rescdsk: `..` also works: `33333..toExponential(2);` – Peter Jaric May 07 '13 at 08:12
  • Or (33333).toExponential(2) – Jonathan Aug 19 '16 at 14:32
  • @ars265: If I understand you right, typical usage would be Math.round(100*x)/100 to get x with 2 digits after the decimal point. This, however, returns a number, so then you are stuck with the number formatting problem afterwards, for all rational numbers that cannot be exactly represented in both binary and decimal form. In contrast, number.toFixed(d) takes care of the formatting and returns a string. BTW, today I used a trick for padding integers with leading zeroes: s = (n/1000).toFixed(3).substring(2); (works for integers below 1000) :-) – Elias Hasle May 09 '19 at 16:50
  • Relevant to this concern and technique is https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart. For example: `String(4).padStart(5,'0')` gives '00004'. – Nathan Chappell Oct 26 '19 at 15:53
310

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.

Community
  • 1
  • 1
Kim
  • 1,361
  • 3
  • 18
  • 24
  • 103
    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 Dec 04 '15 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 https://jsfiddle.net/zvcm70pa/ – inanutshellus May 25 '16 at 03: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)` – cchamberlain May 27 '16 at 06:22
  • Actually, I've since looked at the Mozilla link given in this answer and see that their example tagged template that returns a function isn't a bad way to do i18n-like string tables, where you can pass the returned function the parameters you want to put into the string. Granted, I don't know how that compares in memory size to simply strings you interpolate by replacing {0} markers. – Tustin2121 May 27 '16 at 16:13
  • 13
    this solution won't work for format string passed in variable (from server for example) – user993954 Nov 15 '16 at 14:06
  • 2
    @inanutshellus That works fine if your template function is defined on the same machine where it is executed. As far as I know, you can't pass a function as JSON, so storing template functions in a database doesn't work well. – styfle May 04 '17 at 18:44
  • +1 for using the standard built in function which can be helpful in a lot of situations. [here](https://stackoverflow.com/questions/35694708/html5-es6-template-string-polyfill) a list of polyfills for supporting older browsers – bracco23 Oct 22 '17 at 10:28
  • https://caniuse.com/#feat=template-literals suggests there are still >10% of net denizens using browsers that don't support this. Does it degrade nicely, or explode? – naught101 Feb 06 '19 at 00:20
  • 6
    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 Apr 25 '19 at 10:28
  • 2
    It's nothing close to sprintf. In sprintf, I can use, say, %.2f to print money - but template string is just a dumb wrapper around string concatenation... – Alex Povolotsky Mar 12 '21 at 09:16
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. ;)

int_ua
  • 1,646
  • 2
  • 18
  • 32
Filipiz
  • 1,031
  • 1
  • 8
  • 14
  • 3
    https://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 Jun 27 '11 at 18:46
  • 7
    jsxt is GPL-licensed unfortunately – AndiDog Jul 22 '12 at 20:24
  • Very inefficient approach. Uses regex when not needed, looks up a whole string for searching many times. – Artem Balianytsia Jan 23 '22 at 06:48
120

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

util.format("%s world", "Hello")
Fuhrmanator
  • 11,459
  • 6
  • 62
  • 111
George Eracleous
  • 4,278
  • 6
  • 41
  • 50
  • 2
    This doesn't support %x as of Node v0.10.26 – Max Krohn Apr 24 '14 at 13:16
  • 2
    Doesn't support width and alignment modifiers either (e.g. `%-20s %5.2f`) – FGM Jan 04 '19 at 13:29
  • 5
    I had to scroll all the way down the page to see this useful answer. – Daniel Viglione Apr 14 '20 at 22:50
  • 1
    From [util.format node documentation](https://nodejs.org/docs/latest-v14.x/api/util.html#utilformatformat-args): "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 Dec 15 '22 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")
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Zippo
  • 15,850
  • 10
  • 60
  • 58
  • What about "{{0}} use this next instead {0}"? Not really a `String.Format`, but it is small and nice function to use. – BrunoLM Nov 12 '10 at 16:30
  • 1
    why `+=`?, should it `formatted = this.replace("{" + arg + "}", arguments[arg]);` – guilin 桂林 Nov 30 '10 at 06:30
  • It shouldn't be =, it should be +=, because arguments[arg] may not be a string. Because format is already a string outside the loop, + will concatenate the string, and arguments[arg] will automatically have .toString() called on it. If arguments[0] was 5 and arguments[1] was 7, then instead of "{1}{2}".format(5,7) returning 57, the first argument will yield 12 and the second one will throw an out of bounds exception. – nevelis Dec 03 '10 at 18:08
  • Ahh, but now I see he's adding the whole replacement to the string every time, you're right. user437231's answer is correct. For some reason I was thinking this.replace was only returning the replaced part... – nevelis Dec 03 '10 at 18:11
  • 2
    I think the code is still not correct. The correct one one should be like [Filipiz](http://stackoverflow.com/questions/610406/javascript-printf-string-format#4256130) posted. – wenqiang Jan 11 '11 at 02: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 Feb 06 '11 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 Mar 28 '11 at 14:05
  • 6
    The variable `arg` is global. You need to do this instead: `for (var arg in arguments) {` – Pauan Dec 05 '12 at 22:49
  • `formatted.replace(new RegExp("\\{" + arg + "\\}", "g"), arguments[arg])` allows you to repeat indexes in case you need to duplicate values in the string. – robru Mar 27 '14 at 20:57
  • 1
    The function is unsafe as it sometimes interprets `{n}`s within the strings passed to it: `'{0}'.format('you should only see this {1}', 'you should not see this')` – Marian Sep 11 '18 at 20:03
  • CHANGE `formatted = formatted.replace("{" + arg + "}", arguments[arg]);` TO `formatted = formatted.split('{' + arg + '}').join(arguments[arg]);` – Cyrus Nov 02 '18 at 16:08
67

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")
);
CPHPython
  • 12,379
  • 5
  • 59
  • 71
Monarch Wadia
  • 4,400
  • 4
  • 36
  • 37
  • 5
    Here's a version that uses this approach to create a simplified `printf` function: https://jsfiddle.net/11szrbx9 – Dem Pilafian Jul 20 '15 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 Feb 22 '18 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 Apr 05 '18 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 Jul 06 '18 at 10:19
54

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.

Luke Madhanga
  • 6,871
  • 2
  • 43
  • 47
bart
  • 7,640
  • 3
  • 33
  • 40
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));
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Satish Chandra Gupta
  • 2,970
  • 1
  • 22
  • 22
  • 2
    Thanks! 3rd option suits for my case. – Amila Senadheera Nov 19 '21 at 07:05
  • 1
    3rd option is the only one that allows variables to be "injected" AFTER declaring the string. – Prid Jun 29 '22 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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain#implicit_constructors_of_literals). While not a completely symmetric with `printf`, `util.format` is probably the best choice for what the op is looking for. – Subfuzion Aug 29 '22 at 06: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());
jsxt
  • 1,097
  • 11
  • 28
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'
Daniel
  • 1,364
  • 1
  • 11
  • 18
Rtlprmft
  • 19
  • 2
  • 2
  • unfortunately at least # and + are not implemented for floats. here is a reference for the function in c: https://www.tutorialspoint.com/c_standard_library/c_function_sprintf.htm – Daniel May 29 '18 at 13:40
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! %%"
mauris
  • 42,982
  • 15
  • 99
  • 131
Braden Best
  • 8,830
  • 3
  • 31
  • 43
  • 3
    this answer was great for a quick copy paste into an existing function. No require no downloads etc. – Nick Jun 24 '17 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 Jul 11 '23 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;
};
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
user437231
  • 163
  • 1
  • 2
  • 1
    It didn't work on Firefox. The debugger show arg as undefined. – xiao 啸 Dec 11 '10 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 – samarjit samanta Jan 28 '11 at 01: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 Feb 06 '11 at 21:12
  • You should propose an answer edit instead of duplicate answer. This duplicate [this answer](https://stackoverflow.com/a/3492815/5935198) – alexandre-rousseau May 09 '19 at 09: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.

  • 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 Apr 06 '10 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. – Chuck Kollars Jul 01 '13 at 07: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 Jun 24 '14 at 04: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 Jun 24 '14 at 05: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 Jun 24 '14 at 05: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.

Ralph
  • 31,584
  • 38
  • 145
  • 282
Chris S
  • 64,770
  • 52
  • 221
  • 239
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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
17 of 26
  • 27,121
  • 13
  • 66
  • 85
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

xmojmr
  • 8,073
  • 5
  • 31
  • 54
lior hakim
  • 114
  • 1
  • 8
  • 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 Jul 09 '14 at 06:54
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!");
redestructa
  • 1,182
  • 1
  • 11
  • 11
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."
}
Thiago Mata
  • 2,825
  • 33
  • 32
  • the [^}] part in the regexp is unnecesary.. use {(.*?)} instead, or better {([\s\S]*?)} to match newline too. – rawiro Oct 01 '14 at 08:26
9

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Spudley
  • 166,037
  • 39
  • 233
  • 307
9

There is "sprintf" for JavaScript which you can find at http://www.webtoolkit.info/javascript-sprintf.html.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Pooria
  • 780
  • 1
  • 8
  • 17
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.

Peter
  • 19
  • 1
  • 1
6

For basic formatting:

var template = jQuery.validator.format("{0} is not a valid value");
var result = template("abc");
Evgeny Gerbut
  • 390
  • 1
  • 4
  • 10
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
Murtaza Hussain
  • 3,851
  • 24
  • 30
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;
    });
  };
Afshin Mehrabani
  • 33,262
  • 29
  • 136
  • 201
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

A T
  • 13,008
  • 21
  • 97
  • 158
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!
Yevgeniy Afanasyev
  • 37,872
  • 26
  • 173
  • 191
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).

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Tracker1
  • 19,103
  • 12
  • 80
  • 106
3

I didn't see pyformat in the list so I thought I'd throw it in:

console.log(pyformat( 'The {} {} jumped over the {}'
                , ['brown' ,'fox' ,'foobar']
                ))
console.log(pyformat('The {0} {1} jumped over the {1}'
                , ['brown' ,'fox' ,'foobar']
                ))
console.log(pyformat('The {color} {animal} jumped over the {thing}'
                , [] ,{color: 'brown' ,animal: 'fox' ,thing: 'foobaz'}
                ))
Bovard
  • 1,175
  • 1
  • 14
  • 22
2

I did not see the String.format variant:

String.format = function (string) {
    var args = Array.prototype.slice.call(arguments, 1, arguments.length);
    return string.replace(/{(\d+)}/g, function (match, number) {
        return typeof args[number] != "undefined" ? args[number] : match;
    });
};
jerone
  • 16,206
  • 4
  • 39
  • 57
2

For use with jQuery.ajax() success functions. Pass only a single argument and string replace with the properties of that object as {propertyName}:

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

Example:

var userInfo = ("Email: {Email} - Phone: {Phone}").format({ Email: "someone@somewhere.com", Phone: "123-123-1234" });
2

With sprintf.js in place - one can make a nifty little format-thingy

String.prototype.format = function(){
    var _args = arguments 
    Array.prototype.unshift.apply(_args,[this])
    return sprintf.apply(undefined,_args)
}   
// this gives you:
"{%1$s}{%2$s}".format("1", "0")
// {1}{0}
krichard
  • 3,699
  • 24
  • 34
2
/**
 * Format string by replacing placeholders with value from element with
 * corresponsing index in `replacementArray`.
 * Replaces are made simultaneously, so that replacement values like
 * '{1}' will not mess up the function.
 *
 * Example 1:
 * ('{2} {1} {0}', ['three', 'two' ,'one']) -> 'one two three'
 *
 * Example 2:
 * ('{0}{1}', ['{1}', '{0}']) -> '{1}{0}'
 */
function stringFormat(formatString, replacementArray) {
    return formatString.replace(
        /\{(\d+)\}/g, // Matches placeholders, e.g. '{1}'
        function formatStringReplacer(match, placeholderIndex) {
            // Convert String to Number
            placeholderIndex = Number(placeholderIndex);

            // Make sure that index is within replacement array bounds
            if (placeholderIndex < 0 ||
                placeholderIndex > replacementArray.length - 1
            ) {
                return placeholderIndex;
            }

            // Replace placeholder with value from replacement array
            return replacementArray[placeholderIndex];
        }
    );
}
Lars Gyrup Brink Nielsen
  • 3,939
  • 2
  • 34
  • 35
2

another suggestion is you use the string template:

const getPathDadosCidades = (id: string) =>  `/clientes/${id}`

const getPathDadosCidades = (id: string, role: string) =>  `/clientes/${id}/roles/${role}`
Lucas Breitembach
  • 1,515
  • 11
  • 15
1

I needed a function which could format a price (given in cents) in a way preferred by the user, and the tricky part is that the format is specified by the user -- and I do not expect my users to understand printf-like syntax, or regexps, etc. My solution is somewhat similar to that used in Basic, so the user just marks with # places for digits, for example:

simple_format(1234567,"$ ###,###,###.##")
"$ 12,345.67"
simple_format(1234567,"### ### ###,## pln")
"12 345,67 pln"

I believe this is quite easy to understand by user, and quite easy to implement:

function simple_format(integer,format){
  var text = "";
  for(var i=format.length;i--;){
    if(format[i]=='#'){
      text = (integer%10) + text;
      integer=Math.floor(integer/10);
      if(integer==0){
        return format.substr(0,i).replace(/#(.*#)?/,"")+text;
      }
    }else{
      text = format[i] + text;
    }
  }
  return text;
}
qbolec
  • 5,374
  • 2
  • 35
  • 44
1

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>
NISHANK KUMAR
  • 487
  • 5
  • 7
1

arg function:

/**
 * Qt stil arg()
 * var scr = "<div id='%1' class='%2'></div>".arg("mydiv").arg("mydivClass");
 */
String.prototype.arg = function() {
    var signIndex = this.indexOf("%");
    var result = this;
    if (signIndex > -1 && arguments.length > 0) {
        var argNumber = this.charAt(signIndex + 1);
        var _arg = "%"+argNumber;
        var argCount = this.split(_arg);
        for (var itemIndex = 0; itemIndex < argCount.length; itemIndex++) {
            result = result.replace(_arg, arguments[0]);
        }
    }
    return result;
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
1

There is also Globalize.format in the jQuery Globalize project, the official globalization service for jQuery UI. IT's nice when you need culture-aware formatting.

Craig Stuntz
  • 125,891
  • 12
  • 252
  • 273
1

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'))
Jens Törnell
  • 23,180
  • 45
  • 124
  • 206
1

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]

David Spector
  • 1,520
  • 15
  • 21
0

This one works with {0}, {1} and {}.

String.prototype.format = function format()
{                                                                                                               
  var msg = this;
  for(var i in arguments)
    msg = msg.replace(/\{\}/,arguments[i]).replace(new RegExp('\\{'+i+'\\}','g'),arguments[i]);
  return msg;
}
moechofe
  • 101
  • 6
0

You can use this function

            String.prototype.format = function (args) {
            var str = this;
            return str.replace(String.prototype.format.regex, function(item) {
                var intVal = parseInt(item.substring(1, item.length - 1));
                var replace;
                if (intVal >= 0) {
                    replace = args[intVal];
                } else if (intVal === -1) {
                    replace = "{";
                } else if (intVal === -2) {
                    replace = "}";
                } else {
                    replace = "";
                }
                return replace;
            });
        };
        String.prototype.format.regex = new RegExp("{-?[0-9]+}", "g");

        // Sample usage.
        var str = "She {1} {0}{2} by the {0}{3}. {-1}^_^{-2}";
        str = str.format(["sea", "sells", "shells", "shore"]);
        alert(str);
hienbt88
  • 1,309
  • 1
  • 11
  • 13
  • Using `parseInt()` as @hienbt88 has here yields something surprisingly competitive, performancewise… I benched both a tidied-up version of this idea and a regex-caching variant of same here: http://jsperf.com/stringformat/6#results – the `parseInt()` implementations come out at or near the top (vs. a bunch of the other versions of `String.format()` harvested from snippets posted by this q’s answerers). – fish2000 Dec 02 '13 at 18:04
0

bobjs can do this:

var sFormat = "My name is {0} and I am {1} years old."; 
var result = bob.string.formatString(sFormat, "Bob", 29); 
console.log(result); 
//output: 
//========== 
// My name is Bob and I am 29 years old. 
Tengiz
  • 8,011
  • 30
  • 39
0
String.prototype.repeat = function(n) { 
    return new Array(++n).join(this); 
};

String.prototype.pad = function(requiredLength, paddingStr, paddingType) {    
    var n = requiredLength - this.length; 

    if (n) {
        paddingType = paddingType ? paddingType.toLowerCase() : '';
        paddingStr = paddingStr || ' ';
        paddingStr = paddingStr.repeat( Math.ceil(n / paddingStr.length) ).substr(0, n);

        if (paddingType == 'both') {
            n /= 2;
            return paddingStr.substr( 0, Math.ceil(n) ) + this + paddingStr.substr( 0, Math.floor(n) );
        }   

        if (paddingType == 'left') {
            return paddingStr + this;
        }

        return this + paddingStr;
    } 

    return this; 
}; 

// синтаксис аналогичен printf
// 'Привет, %s!'.format('мир') -> "Привет, мир!"
// '%.1s.%.1s. %s'.format('Иван', 'Иванович', 'Иванов') -> "И.И. Иванов"
String.prototype.format = function() {
    var i = 0, 
        params = arguments;

    return this.replace(/%(?:%|(?:(|[+-]+)(|0|'.+?)([1-9]\d*)?(?:\.([1-9]\d*))?)?(s|d|f))/g, function(match, sign, padding, width, precision, type) {
        if (match == '%%') { 
            return '%'; 
        }

        var v = params[i++];

        if (type == 'd') { 
            v = Math.round(v); 
        }
        else if (type == 'f') {
            v = v.toFixed(precision ? precision : 6);
        }

        if (/\+/.test(sign) && v > 0) {
            v = '+' + v;
        }

        v += '';

        if (type != 'f' && precision) {
            v = v.substr(0, precision);
        }

        if (width) {
            v = v.pad(width, padding == '' ? ' ' : padding[0] == "'" ? padding.substr(1) : padding, /-/.test(sign) ? 'right' : 'left'); 
        }

        return v;
    });
};

// this.name = 'Вася';
// console.log( 'Привет, ${name}!'.template(this) );
// "Привет, Вася!"
String.prototype.template = function(context) {
    return this.replace(/\$\{(.*?)\}/g, function(match, name) {
        return context[name];
    });
};
user2240578
  • 87
  • 2
  • 6
0

This is an implementation of https://stackoverflow.com/a/4673436/1258486 for CoffeeScript.

https://gist.github.com/eces/5669361

if String.prototype.format is undefined
  String.prototype.format = () ->
    _arguments = arguments
    this.replace /{(\d+)}/g, (match, number) ->
      if typeof _arguments[number] isnt 'undefined' then _arguments[number] else match
Community
  • 1
  • 1
eces
  • 63
  • 6
0

I started porting the Java String.format (actually new Formatter().format()) to javascript. The initial version is available at:

https://github.com/RobAu/javascript.string.format

You can simple add the javscript and call StringFormat.format("%.2f", [2.4]); etc.

Please note it is NOT finished yet, but feedback is welcome :)

Rob Audenaerde
  • 19,195
  • 10
  • 76
  • 121
0
export function stringFormat (str: string, ...args: string[]) {
     return args.reduce((acc, curr, i) => acc.replace(new RegExp("\\{" + i + "\\}", 'g'), curr), str);
}
Potherca
  • 13,207
  • 5
  • 76
  • 94
Mahendra Hirapra
  • 379
  • 4
  • 13
0

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

Example

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'));
TheMisir
  • 4,083
  • 1
  • 27
  • 37
0

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
Nero
  • 1,555
  • 1
  • 13
  • 28
0

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?

Alex Povolotsky
  • 381
  • 2
  • 4
  • 10
  • Formatting outside the string. `(213.3244).toFixed(2)` exists in JavaScript, other languages have other methods (C# has `Decimal.Truncate()`, or you use `Math.Round()`/`Math.round()`). Cultural information is also present in many languages. `System.Globalization` for C# is an example, `(324).toLocaleString()` in JavaScript. This answer also only works in Node.js, not the browser. Considering the question doesn't specify if its browser or node, this answer could be considered rather useless to the situation (tho helpful to others coming here). Template strings are the best solution here. – Lilly Apr 30 '21 at 11:12
0

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

ßiansor Å. Ålmerol
  • 2,849
  • 3
  • 22
  • 24
0

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.

Sean Morris
  • 384
  • 2
  • 9
0

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

Mkoes
  • 70
  • 4
0

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!"));
Francisco M
  • 163
  • 2
  • 9
0

I am answering this question because of the following reasons.

  • sprintf/printf allows you to provide variable lengths of arguments.
  • The top answers are using template literals. When using template literals you must know the number of arguments you have.

@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:

  • In the method, you're passing your desired template string in the first parameter, having placeholder :param. Alongside that, you can pass as many replacers as you want.
  • It then iterates over the values passed and replaces the next :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));
ssi-anik
  • 2,998
  • 3
  • 23
  • 52
-1

This is not an exact duplicate of sprintf; however, it is similar and more powerful: https://github.com/anywhichway/stringformatter

Format expressions using this library take the form of embedded Javascript objects, e.g.

format("I have {number: {currency: "$", precision:2}}.",50.2); 

will return "I have $50.20.".

Adam Michalik
  • 9,678
  • 13
  • 71
  • 102
AnyWhichWay
  • 716
  • 8
  • 11
-1

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

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

GHosT
  • 181
  • 2
  • 10
-1

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) );
Sean Morris
  • 77
  • 2
  • 3