はじめに
コードだけ知れればOKという方は、TypeScriptでの実装までジャンプしてください!
経緯
以下にあるように、C#やJava, Pythonには当たり前のように組み込まれている String.format()
はTypeScriptには存在しない(らしい)
テンプレートリテラルと呼ばれるものを使えば似たようなことはできるらしいですが、ログ出力なんかを考えると format()
みたいなのが使いたい時があるんですよね。
const name = 'taro', age = 21
console.log(`name: ${name}, age: ${age}`)
// -> name: taro, age: 21
上記記事だとString
クラスのprototype
拡張としてメソッドを定義しているので、共通利用関数として使用できるように作ってみました。
C#の例
csharp.cs
using System;
public class Hello{
public static void Main(){
Console.WriteLine(String.Format("name: {0}, age: {1}","taro", 21));
// -> name: taro, age: 21
}
}
pythonの例
python.py
print('name: {0}, age: {1}'.format('taro', 21))
# -> name: taro, age: 21
Javaの例
Java.java
public class Main {
public static void main(String[] args) throws Exception {
System.out.println(String.format("name: %s, age: %d", "taro", 21));
// -> name: taro, age: 21
}
}
TypeScriptでの実装
typescript.ts
/**
* JavaやC#のformatメソッドのように、特定の文字列のプレースホルダを引数で渡された文字で置き換える
*
* @param str 置換前文字列 プレースホルダを`{0}`, `{1}`の形式で埋め込む
* @param ...args 第2引数以降で、置換する文字列を指定する
*
* 参考
* - https://stackoverflow.com/questions/610406/javascript-equivalent-to-printf-string-format/4673436#4673436
* - https://trueman-developer.blogspot.com/2015/11/typescriptjavascript.html
*
* ### Sample
* ```ts
* format('{0}とは、{1}までに身に付けた{2}の{3}である。', '常識', '18歳', '偏見', 'コレクション')
* format('{0}とは、{1}までに身に付けた{2}の{3}である。', ...['常識', '18歳', '偏見', 'コレクション'])
* ```
* →`'常識とは、18歳までに身に付けた偏見のコレクションである。'`
*/
export const format = (str: string, ...args: unknown[]): string => {
for (const [i, arg] of args.entries()) {
const regExp = new RegExp(`\\{${i}\\}`, 'g')
str = str.replace(regExp, arg as string)
}
return str
}
JavaScriptでも使える
型定義さえ外せば、JavaScriptとしても動かせる
javascript.js
const format = (str, ...args) => {
for (const [i, arg] of args.entries()) {
const regExp = new RegExp(`\\{${i}\\}`, 'g')
str = str.replace(regExp, arg)
}
return str
}
↑ChromeのConsoleタブで動かしてみた
簡単な使い方
format('{0}とは、{1}までに身に付けた{2}の{3}である。', '常識', '18歳', '偏見', 'コレクション')
// -> '常識とは、18歳までに身に付けた偏見のコレクションである。'
第1引数
文字列を渡す
置き換えたい箇所を{0}
形式で文字列に埋め込む
第2引数以降
順番に置き換える文字列や数値を記述する
jest
でのテストコード
こちらから仕様を読み取ってもらえたら嬉しいです
format.spec.ts
it('format', () => {
// stringではなくnumberを渡されても正常に動作する
expect(format('age: {0}', 25)).toBe('age: 25')
// 複数の置換があっても動作する
expect(format('{0} {1} へのリクエストが失敗しました。', 'GET', '/users')).toBe(
'GET /users へのリクエストが失敗しました。'
)
expect(
format('{0}とは、{1}までに身に付けた{2}の{3}である。', '常識', '18歳', '偏見', 'コレクション')
).toBe('常識とは、18歳までに身に付けた偏見のコレクションである。')
// null・undefinedチェック
expect(format('null: {0}, undefined: {1}', null, undefined)).toBe(
'null: null, undefined: undefined'
)
// ブランク文字列チェック
expect(format('blank: {0}', '')).toBe('blank: ')
// プレースホルダと置換文字列の数が一致していない場合の確認
expect(format('{0} is dead, but {1} is alive! {0} {2}', 'ASP', 'ASP.NET')).toBe(
'ASP is dead, but ASP.NET is alive! ASP {2}'
)
expect(format('{0}べきか、{1}べきか', '生きる', '死ぬ', '問題')).toBe(
'生きるべきか、死ぬべきか'
)
// 序数の順序が逆
expect(format('やはらかに 柳あをめる北上の {1} {0}', '泣けと如くに', '岸辺目に見ゆ')).toBe(
'やはらかに 柳あをめる北上の 岸辺目に見ゆ 泣けと如くに'
)
// プレースホルダが複数ある場合
expect(
format(
'{0}にて、アニメ「{1}×{1}」が{2}より毎週水曜午前1時59分(火曜深夜)から「Anichu」枠にて再放送することが決定いたしました',
'日本テレビ',
'HUNTER',
'4月'
)
).toBe(
'日本テレビにて、アニメ「HUNTER×HUNTER」が4月より毎週水曜午前1時59分(火曜深夜)から「Anichu」枠にて再放送することが決定いたしました'
)
// 配列をスプレッド演算子で展開した場合
expect(
format(
'{0}とは、{1}までに身に付けた{2}の{3}である。',
...['常識', '18歳', '偏見', 'コレクション']
)
).toBe('常識とは、18歳までに身に付けた偏見のコレクションである。')
// 置換文字を渡されなくても正常に動作する
expect(format('生きるべきか、死ぬべきか')).toBe('生きるべきか、死ぬべきか')
expect(format('生きるべきか、死ぬべきか', [])).toBe('生きるべきか、死ぬべきか')
expect(format('生きるべきか、死ぬべきか', null)).toBe('生きるべきか、死ぬべきか')
expect(format('生きるべきか、死ぬべきか', undefined)).toBe('生きるべきか、死ぬべきか')
})