JavaScript Tips | 文字列操作:kebab-caseに変換する

JavaScript JavaScript
スポンサーリンク
スポンサーリンク

業務・実務で使えるJavaScriptユーティリティ:kebab-caseに変換する方法

プログラミングの現場では、「UserName」のようなパスカルケースや「userName」のようなキャメルケースを、「user-name」のようなケバブケースに変換する必要が頻繁に生じます。今回は、様々な形式の文字列をケバブケースに統一するユーティリティ関数について、ステップバイステップで解説します。

なぜkebab-caseに変換する必要があるのか

実務では、URLのパス、CSSクラス名、ファイル名、HTML属性値など、特定の識別子にケバブケースを使用する場面が多くあります。特にWeb開発では、URLの可読性を高めるためや、CSSの命名規則として、ケバブケースが広く採用されています。

例えば、JavaScriptのコード内で「userName」という変数を使用している場合、URLパラメータやCSSクラス名としては「user-name」に変換する必要があります。また、APIのレスポンスで受け取った「UserName」を、フロントエンドのルーティング用の「user-name」に変換したい場合などが典型的です。

具体的な使用場面

  • JavaScriptの変数名をURLパラメータに変換(例:「userName」→「user-name」)
  • コンポーネント名をCSSクラス名に変換(例:「UserProfile」→「user-profile」)
  • ファイル名やディレクトリ名の生成(例:「UserService」→「user-service」)
  • HTML属性値の生成(例:「dataValue」→「data-value」)
  • ルーティングパスの生成(例:「OrderHistory」→「order-history」)

基本的な実装方法

ケバブケースへの変換は、主に以下の3つのパターンに対応する必要があります。それぞれのパターンを段階的に実装していきましょう。

パターン1:キャメルケースからケバブケースへの変換

大文字で区切られた文字列を、ハイフン(-)で区切り、すべて小文字に変換する方法です。

// キャメルケースをケバブケースに変換する関数
function camelToKebab(str) {
  // 空文字やundefinedの場合はそのまま返す(エラー防止)
  if (!str) return str;
  
  // 大文字の前にハイフンを挿入し、すべて小文字に変換
  return str
    .replace(/([A-Z])/g, '-$1')  // 大文字の前に-を追加
    .toLowerCase()               // すべて小文字に変換
    .replace(/^-/, '');          // 先頭のハイフンを削除(最初の文字が大文字の場合)
}

// 使用例
console.log(camelToKebab('userName'));      // 'user-name'
console.log(camelToKebab('userEmail'));     // 'user-email'
console.log(camelToKebab('orderId'));       // 'order-id'
console.log(camelToKebab('user'));          // 'user'(変換不要な場合はそのまま)
JavaScript

コードの解説

この関数は3つのステップで動作します。

  1. ハイフンの挿入:replace(/([A-Z])/g, '-$1')で、すべての大文字の前にハイフンを挿入します。$1はキャプチャした大文字を表します。
  2. 小文字への変換:toLowerCase()で文字列全体を小文字に変換します。
  3. 先頭のハイフン削除:replace(/^-/, '')で、もし先頭にハイフンがあれば削除します。これは「UserName」のようなパスカルケースに対応するためです。

パターン2:パスカルケースからケバブケースへの変換

キャメルケースとほぼ同様ですが、先頭が大文字の場合に対応する必要があります。

// パスカルケースをケバブケースに変換する関数
function pascalToKebab(str) {
  // 空文字やundefinedの場合はそのまま返す
  if (!str) return str;
  
  // 先頭の大文字を小文字に変換し、その後キャメルケースと同じ処理
  return str
    .replace(/([A-Z])/g, '-$1')  // 大文字の前に-を追加
    .toLowerCase()               // すべて小文字に変換
    .replace(/^-/, '');          // 先頭のハイフンを削除
}

// 使用例
console.log(pascalToKebab('UserName'));      // 'user-name'
console.log(pascalToKebab('UserEmail'));     // 'user-email'
console.log(pascalToKebab('OrderId'));       // 'order-id'
JavaScript

連続した大文字への対応

「HTMLParser」のような略語を含む文字列の場合、単純な変換では「h-t-m-l-parser」のような不自然な結果になります。より洗練された変換が必要な場合は、以下のような対応を検討してください。

// より洗練されたケバブケース変換(連続大文字への配慮版)
function pascalToKebabAdvanced(str) {
  if (!str) return str;
  
  return str
    // 連続する大文字の区切りを適切に処理(HTMLParser → html-parser)
    .replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2')
    // 小文字と大文字の区切りを処理(HtmlParser → html-parser)
    .replace(/([a-z\d])([A-Z])/g, '$1-$2')
    // すべて小文字に変換
    .toLowerCase();
}

// 使用例
console.log(pascalToKebabAdvanced('UserName'));      // 'user-name'
console.log(pascalToKebabAdvanced('HTMLParser'));    // 'html-parser'(自然な結果)
console.log(pascalToKebabAdvanced('URL'));           // 'url'(すべて大文字の場合)
JavaScript

パターン3:スネークケースからケバブケースへの変換

アンダースコア(_)で区切られた文字列を、ハイフン(-)に置き換え、すべて小文字に変換する方法です。

// スネークケースをケバブケースに変換する関数
function snakeToKebab(str) {
  // 空文字やundefinedの場合はそのまま返す
  if (!str) return str;
  
  // アンダースコアをハイフンに置き換え、小文字に変換
  return str
    .replace(/_/g, '-')   // アンダースコアをハイフンに置き換え
    .toLowerCase();       // すべて小文字に変換
}

// 使用例
console.log(snakeToKebab('user_name'));      // 'user-name'
console.log(snakeToKebab('user_email'));     // 'user-email'
console.log(snakeToKebab('order_id'));       // 'order-id'
JavaScript

汎用的なkebab-case変換関数の実装

実務では、どの形式で入力されるか分からない場合があります。そこで、キャメルケース・パスカルケース・スネークケースのいずれにも対応できる汎用的な関数を作成しましょう。

統合版ケバブケース変換関数

/**
 * 任意の文字列をケバブケースに変換する汎用関数
 * @param {string} str - 変換対象の文字列
 * @returns {string} ケバブケースに変換された文字列
 */
function toKebabCase(str) {
  // 入力チェック(null、undefined、空文字対策)
  if (!str || typeof str !== 'string') {
    return '';
  }

  // ステップ1: 文字列の正規化
  let result = str
    // 連続する大文字の区切りを適切に処理(HTMLParser → html-parser)
    .replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2')
    // 小文字と大文字の区切りを処理(userName → user-name)
    .replace(/([a-z\d])([A-Z])/g, '$1-$2')
    // アンダースコアとスペースをハイフンに置き換え
    .replace(/[_\s]+/g, '-')
    // 連続するハイフンを1つにまとめる
    .replace(/-+/g, '-')
    // 先頭と末尾のハイフンを削除
    .replace(/^-+|-+$/g, '');

  // ステップ2: すべて小文字に変換
  return result.toLowerCase();
}

// 使用例
console.log(toKebabCase('userName'));          // 'user-name'
console.log(toKebabCase('UserName'));          // 'user-name'
console.log(toKebabCase('user_name'));         // 'user-name'
console.log(toKebabCase('user name'));         // 'user-name'
console.log(toKebabCase('USER_NAME'));         // 'user-name'
console.log(toKebabCase('user-name'));         // 'user-name'(既にケバブケース)
console.log(toKebabCase('HTMLParser'));        // 'html-parser'
console.log(toKebabCase('userId123'));         // 'user-id-123'
console.log(toKebabCase('  user  name  '));   // 'user-name'(スペースも処理)
JavaScript

コードの解説

この関数は複数の正規表現を順番に適用して、文字列を正規化していきます。

  1. 連続大文字の処理:([A-Z]+)([A-Z][a-z])で、「HTMLParser」のような連続する大文字を適切に区切ります。
  2. キャメルケースの処理:([a-z\d])([A-Z])で、「userName」のような小文字と大文字の区切りを処理します。
  3. アンダースコアとスペースの処理:[_\s]+で、アンダースコアやスペースをハイフンに置き換えます。
  4. ハイフンの正規化:連続するハイフンを1つにまとめ、先頭と末尾のハイフンを削除します。
  5. 小文字への変換:最後にすべて小文字に変換します。

重要な注意点:正規表現の順序

上記の正規表現は順序が重要です。連続大文字の処理を先に行わないと、「HTMLParser」が「h-t-m-l-parser」のような不自然な結果になってしまいます。正規表現を組み合わせる場合は、処理の順序を慎重に検討してください。

より実践的な応用パターン

実際の業務では、単一の文字列だけでなく、オブジェクトのキーや配列の要素を一括で変換する場面が多くあります。

1. オブジェクトのキーを一括変換

APIから受け取ったオブジェクトのすべてのキーをケバブケースに変換したい場合、以下のような実装が有効です。

/**
 * オブジェクトのキーをすべてケバブケースに変換
 * @param {Object} obj - 変換対象のオブジェクト
 * @returns {Object} キーがケバブケースに変換された新しいオブジェクト
 */
function objectKeysToKebab(obj) {
  // オブジェクトでない場合はそのまま返す
  if (typeof obj !== 'object' || obj === null) {
    return obj;
  }
  
  // 配列の場合は各要素に再帰的に適用
  if (Array.isArray(obj)) {
    return obj.map(item => objectKeysToKebab(item));
  }
  
  // オブジェクトの各キーを変換
  return Object.keys(obj).reduce((acc, key) => {
    // キーをケバブケースに変換
    const kebabKey = toKebabCase(key);
    // 値もオブジェクトの場合は再帰的に変換
    acc[kebabKey] = objectKeysToKebab(obj[key]);
    return acc;
  }, {});
}

// 使用例
const apiResponse = {
  userName: 'Taro',
  userEmail: '[email protected]',
  addressInfo: {
    zipCode: '123-4567',
    cityName: 'Tokyo'
  },
  orderHistory: [
    { orderId: 1, orderDate: '2024-01-01' },
    { orderId: 2, orderDate: '2024-01-02' }
  ]
};

const kebabCased = objectKeysToKebab(apiResponse);
console.log(kebabCased);
// 結果:
// {
//   'user-name': 'Taro',
//   'user-email': '[email protected]',
//   'address-info': {
//     'zip-code': '123-4567',
//     'city-name': 'Tokyo'
//   },
//   'order-history': [
//     { 'order-id': 1, 'order-date': '2024-01-01' },
//     { 'order-id': 2, 'order-date': '2024-01-02' }
//   ]
// }
JavaScript

再帰処理の重要性

上記の関数は「再帰(自分自身を呼び出す処理)」を使用しています。これにより、ネストされたオブジェクトや配列の中身も完全に変換できます。実務では、APIのレスポンスが深くネストされていることが多いため、この再帰的なアプローチが非常に重要です。

2. 配列内の文字列を一括変換

// 配列内のすべての文字列をケバブケースに変換
function arrayToKebab(arr) {
  if (!Array.isArray(arr)) return arr;
  
  return arr.map(item => {
    if (typeof item === 'string') {
      return toKebabCase(item);
    }
    return item; // 文字列でない場合はそのまま
  });
}

// 使用例
const columns = ['userName', 'userEmail', 'orderId'];
const kebabColumns = arrayToKebab(columns);
console.log(kebabColumns); // ['user-name', 'user-email', 'order-id']
JavaScript

3. 安全な文字列変換(null・undefined対策)

実務では、予期せぬタイミングでnullやundefinedが渡されることがあります。より堅牢な関数を作成しましょう。

// 型チェックを厳密に行う安全な関数
function safeToKebab(str) {
  // 文字列型でない、または空文字の場合は空文字を返す
  if (typeof str !== 'string' || str.length === 0) {
    return '';
  }
  
  // 前後のスペースを除去してから変換
  const trimmed = str.trim();
  return toKebabCase(trimmed);
}

// 使用例
console.log(safeToKebab('  userName  '));  // 'user-name'(スペースも除去)
console.log(safeToKebab(null));              // ''(エラーにならない)
console.log(safeToKebab(123));               // ''(数値でもエラーにならない)
console.log(safeToKebab(undefined));         // ''(エラーにならない)
JavaScript

よくある間違いと注意点

初心者の方がつまずきやすいポイントを3つ挙げます。

1. 文字列の変更不可性を理解していない

JavaScriptの文字列は「イミュータブル(変更不可)」です。つまり、replace()やtoLowerCase()を実行しても元の文字列は変わらず、新しい文字列が返されるだけです。以下のコードは間違いです。

// 間違った例
let str = 'userName';
str.replace(/([A-Z])/g, '-$1');
str.toLowerCase();
console.log(str); // 依然として 'userName'

// 正しい例
let str = 'userName';
str = str.replace(/([A-Z])/g, '-$1').toLowerCase();
console.log(str); // 'user-name'
JavaScript

2. 連続した大文字の扱い

「HTMLParser」や「URL」のような連続した大文字を含む文字列は、単純な変換では意図しない結果になることがあります。以下の例を確認してください。

// 単純な変換の問題点
function simpleCamelToKebab(str) {
  return str.replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '');
}

console.log(simpleCamelToKebab('userName'));     // 'user-name'(問題なし)
console.log(simpleCamelToKebab('HTMLParser'));   // 'h-t-m-l-parser'(不自然)

// 対策:連続する大文字を適切に処理
function advancedCamelToKebab(str) {
  return str
    .replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2')
    .replace(/([a-z\d])([A-Z])/g, '$1-$2')
    .toLowerCase();
}

console.log(advancedCamelToKebab('userName'));     // 'user-name'(自然)
console.log(advancedCamelToKebab('HTMLParser'));   // 'html-parser'(自然)
JavaScript

3. 日本語を含む文字列への対応

日本語には大文字・小文字の概念がないため、日本語を含む文字列の場合は注意が必要です。以下のコードを確認してください。

// 日本語を含む場合
console.log(toKebabCase('ユーザー名'));        // 'ユーザー名'(変化なし)
console.log(toKebabCase('userName名前'));      // 'user-name名前'(日本語はそのまま)
console.log(toKebabCase('user_name_テスト'));  // 'user-name-テスト'(日本語はそのまま)
JavaScript

日本語を含む場合でも、基本的には区切り文字(大文字やアンダースコア)で区切られた部分が正しく処理されます。ただし、日本語同士が連続している場合は、単語の区切りとして認識されないため、意図した結果にならないことがあります。

実務で使えるテンプレート集

コピー&ペーストですぐに使える形でまとめました。プロジェクトの共通ユーティリティファイルに保存しておくと便利です。

/**
 * 文字列ケース変換ユーティリティ集
 * 業務・実務で使用する文字列操作の関数群
 */

const CaseConverter = {
  /**
   * キャメルケースをケバブケースに変換
   * @param {string} str - 変換対象の文字列
   * @returns {string} ケバブケースに変換された文字列
   */
  camelToKebab: function(str) {
    if (!str || typeof str !== 'string') return '';
    return str
      .replace(/([A-Z])/g, '-$1')
      .toLowerCase()
      .replace(/^-/, '');
  },

  /**
   * パスカルケースをケバブケースに変換
   * @param {string} str - 変換対象の文字列
   * @returns {string} ケバブケースに変換された文字列
   */
  pascalToKebab: function(str) {
    if (!str || typeof str !== 'string') return '';
    return str
      .replace(/([A-Z])/g, '-$1')
      .toLowerCase()
      .replace(/^-/, '');
  },

  /**
   * スネークケースをケバブケースに変換
   * @param {string} str - 変換対象の文字列
   * @returns {string} ケバブケースに変換された文字列
   */
  snakeToKebab: function(str) {
    if (!str || typeof str !== 'string') return '';
    return str
      .replace(/_/g, '-')
      .toLowerCase();
  },

  /**
   * 任意の文字列をケバブケースに変換(汎用版)
   * @param {string} str - 変換対象の文字列
   * @returns {string} ケバブケースに変換された文字列
   */
  toKebabCase: function(str) {
    if (!str || typeof str !== 'string') return '';
    
    return str
      .replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2')
      .replace(/([a-z\d])([A-Z])/g, '$1-$2')
      .replace(/[_\s]+/g, '-')
      .replace(/-+/g, '-')
      .replace(/^-+|-+$/g, '')
      .toLowerCase();
  },

  /**
   * オブジェクトのキーをすべてケバブケースに変換
   * @param {Object} obj - 変換対象のオブジェクト
   * @returns {Object} キーがケバブケースに変換された新しいオブジェクト
   */
  objectKeysToKebab: function(obj) {
    if (typeof obj !== 'object' || obj === null) return obj;
    
    if (Array.isArray(obj)) {
      return obj.map(item => this.objectKeysToKebab(item));
    }
    
    return Object.keys(obj).reduce((acc, key) => {
      const kebabKey = this.toKebabCase(key);
      acc[kebabKey] = this.objectKeysToKebab(obj[key]);
      return acc;
    }, {});
  }
};

// 使用例
console.log(CaseConverter.camelToKebab('userName'));           // 'user-name'
console.log(CaseConverter.pascalToKebab('UserName'));          // 'user-name'
console.log(CaseConverter.snakeToKebab('user_name'));          // 'user-name'
console.log(CaseConverter.toKebabCase('userName'));            // 'user-name'
console.log(CaseConverter.toKebabCase('UserName'));            // 'user-name'
console.log(CaseConverter.toKebabCase('user_name'));           // 'user-name'

// オブジェクトの変換例
const data = { userName: 'Taro', addressInfo: { zipCode: '123' } };
console.log(CaseConverter.objectKeysToKebab(data));
// { 'user-name': 'Taro', 'address-info': { 'zip-code': '123' } }
JavaScript

オブジェクト化するメリット

関数をオブジェクト(CaseConverter)にまとめることで、以下のような利点があります。

  • 名前空間の汚染を防ぐ:グローバル変数と競合しにくくなります。
  • コードの見通しが良くなる:CaseConverter.camelToKebab()と書くことで、文字列のケース変換であることが明確になります。
  • 保守性が向上する:関連する関数が一箇所に集約されるため、修正や追加が容易になります。
  • thisによる相互参照:objectKeysToKebab内でthis.toKebabCaseを呼び出すことで、同じオブジェクト内の他のメソッドを利用できます。

まとめ

ケバブケースへの変換は、URL設計やCSS命名、ファイル名生成など、フロントエンド開発で特に重要な処理です。今回紹介したように、キャメルケース・パスカルケース・スネークケースという異なる形式から、一貫したケバブケースへの変換を実現できます。

特に重要なのは、「入力値の検証を必ず行う」こと、「文字列は変更不可であることを理解する」こと、そして「連続した大文字や日本語などの特殊なケースへの配慮」の3点です。これらを押さえておけば、実務で発生する様々なケースに対応できるようになります。

まずは基本形のcamelToKebabやpascalToKebabから使い始め、必要に応じて汎用的なtoKebabCaseやオブジェクト一括変換に拡張していくことをお勧めします。チームで共有するユーティリティとして整備することで、コードの一貫性と生産性が大幅に向上するはずです。

タイトルとURLをコピーしました