9
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

TypeScript(JavaScript)でString.format()ぽいことをするには

Last updated at Posted at 2023-01-01

はじめに

コードだけ知れれば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
}

image.png

↑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('生きるべきか、死ぬべきか')
})
9
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?