JavaScript Tips | 文字列操作:camelCaseに変換する

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

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

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

なぜcamelCaseに変換する必要があるのか

実務では、異なるシステム間でデータをやり取りする際、命名規則の違いが大きな課題となります。データベースのカラム名は「user_name」(スネークケース)、APIのレスポンスは「UserName」(パスカルケース)、そしてJavaScriptの変数名は「userName」(キャメルケース)と、それぞれ異なる形式を採用していることが一般的です。

特にJavaScriptでは、変数名・関数名・プロパティ名にキャメルケースを使用することが標準的な命名規則とされています。**MDN Web Docs**においても、JavaScriptの識別子にはキャメルケースを使用することが推奨されています。

具体的な使用場面

  • データベースのカラム名をJavaScriptの変数名に変換(例:「user_name」→「userName」)
  • APIレスポンスのフィールド名を統一(例:「UserName」→「userName」)
  • URLのクエリパラメータをオブジェクトのプロパティに変換(例:「user-id」→「userId」)
  • フォームのname属性をJavaScriptの変数名にマッピング(例:「user-email」→「userEmail」)

基本的な実装方法

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

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

アンダースコア(_)で区切られた文字列を、単語の先頭を大文字にして連結する方法です。

// スネークケースをキャメルケースに変換する関数
function snakeToCamel(str) {
  // 空文字やundefinedの場合はそのまま返す(エラー防止)
  if (!str) return str;
  
  // アンダースコアで分割し、各単語を処理
  return str.replace(/_([a-z])/g, function(match, letter) {
    // アンダースコアを削除し、次の文字を大文字に変換
    return letter.toUpperCase();
  });
}

// 使用例
console.log(snakeToCamel('user_name'));      // 'userName'
console.log(snakeToCamel('user_email'));     // 'userEmail'
console.log(snakeToCamel('order_id'));       // 'orderId'
console.log(snakeToCamel('user'));           // 'user'(変換不要な場合はそのまま)
JavaScript

コードの解説

この関数は正規表現を使用して、以下のステップで動作します。

  1. 正規表現のパターン:_([a-z])は「アンダースコアの後に小文字が続く」部分にマッチします。
  2. キャプチャグループ:([a-z])で、アンダースコアの後の文字をキャプチャ(捕捉)します。
  3. 置換処理:マッチした部分を、キャプチャした文字の大文字に置き換えます。これにより、アンダースコアは削除され、次の文字が大文字になります。

パターン2:パスカルケースからキャメルケースへの変換

先頭文字だけを小文字にするシンプルな変換です。

// パスカルケースをキャメルケースに変換する関数
function pascalToCamel(str) {
  // 空文字やundefinedの場合はそのまま返す
  if (!str) return str;
  
  // 先頭文字を小文字にし、残りと結合
  return str.charAt(0).toLowerCase() + str.slice(1);
}

// 使用例
console.log(pascalToCamel('UserName'));    // 'userName'
console.log(pascalToCamel('OrderId'));     // 'orderId'
console.log(pascalToCamel('HTMLParser'));  // 'hTMLParser'(連続大文字はこのまま)
JavaScript

連続した大文字への対応

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

// より洗練されたキャメルケース変換(連続大文字への配慮版)
function pascalToCamelAdvanced(str) {
  if (!str) return str;
  
  // 先頭の連続する大文字を除く、最初の大文字を小文字に
  return str.replace(/^([A-Z]+)([A-Z][a-z])/, function(match, p1, p2) {
    return p1.toLowerCase() + p2;
  }).replace(/^([A-Z])/, function(match) {
    return match.toLowerCase();
  });
}

// 使用例
console.log(pascalToCamelAdvanced('UserName'));     // 'userName'
console.log(pascalToCamelAdvanced('HTMLParser'));   // 'htmlParser'(より自然な結果)
console.log(pascalToCamelAdvanced('URL'));          // 'url'(すべて大文字の場合)
JavaScript

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

ハイフン(-)で区切られた文字列を変換する方法です。

// ケバブケースをキャメルケースに変換する関数
function kebabToCamel(str) {
  // 空文字やundefinedの場合はそのまま返す
  if (!str) return str;
  
  // ハイフンで分割し、各単語を処理
  return str.replace(/-([a-z])/g, function(match, letter) {
    // ハイフンを削除し、次の文字を大文字に変換
    return letter.toUpperCase();
  });
}

// 使用例
console.log(kebabToCamel('user-name'));      // 'userName'
console.log(kebabToCamel('user-email'));     // 'userEmail'
console.log(kebabToCamel('order-id'));       // 'orderId'
JavaScript

汎用的なcamelCase変換関数の実装

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

統合版キャメルケース変換関数

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

  // ステップ1: 単語の区切りを明確にするための正規表現
  // アンダースコア、ハイフン、スペース、大文字(単語の区切り)を検出
  const words = str.match(/[A-Z]{2,}(?![a-z])|[A-Z]?[a-z]+|[A-Z]|[0-9]+/g);
  
  // 単語が見つからない場合はそのまま返す
  if (!words) {
    return str;
  }

  // ステップ2: 最初の単語は小文字、以降の単語は先頭を大文字に
  return words.map((word, index) => {
    // 最初の単語はすべて小文字に
    if (index === 0) {
      return word.toLowerCase();
    }
    // 2番目以降の単語は先頭を大文字、残りを小文字に
    return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
  }).join('');
}

// 使用例
console.log(toCamelCase('user_name'));          // 'userName'
console.log(toCamelCase('user-name'));          // 'userName'
console.log(toCamelCase('UserName'));           // 'userName'
console.log(toCamelCase('user name'));          // 'userName'
console.log(toCamelCase('USER_NAME'));          // 'userName'
console.log(toCamelCase('userName'));           // 'userName'(既にキャメルケース)
console.log(toCamelCase('html_parser'));        // 'htmlParser'
console.log(toCamelCase('user_id_123'));        // 'userId123'
JavaScript

コードの解説

この関数は2つの主要なステップで動作します。

  1. 単語の分割:正規表現[A-Z]{2,}(?![a-z])|[A-Z]?[a-z]+|[A-Z]|[0-9]+を使用して、文字列を意味のある単位に分割します。
    • [A-Z]{2,}(?![a-z]):連続する大文字(HTML、URLなど)を1つの単語として認識
    • [A-Z]?[a-z]+:大文字で始まり小文字が続く単語(User、Nameなど)
    • [A-Z]:単独の大文字
    • [0-9]+:数字の連続
  2. キャメルケースへの組み立て:最初の単語はすべて小文字に、2番目以降の単語は先頭を大文字にして連結します。

重要な注意点:正規表現の複雑さ

上記の正規表現は強力ですが、複雑です。業務では、以下のような判断が必要です。

  • シンプルさを優先:入力形式が限定的(スネークケースのみなど)であれば、専用の関数を使用した方が保守しやすいです。
  • パフォーマンス:極端に長い文字列や、大量の変換を行う場合は、正規表現の複雑さが影響する可能性があります。
  • 可読性:チームのスキルレベルに応じて、コメントを充実させるか、シンプルな実装に留めるかを検討してください。

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

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

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

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

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

// 使用例
const apiResponse = {
  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' }
  ]
};

const camelCased = objectKeysToCamel(apiResponse);
console.log(camelCased);
// 結果:
// {
//   userName: 'Taro',
//   userEmail: '[email protected]',
//   addressInfo: {
//     zipCode: '123-4567',
//     cityName: 'Tokyo'
//   },
//   orderHistory: [
//     { orderId: 1, orderDate: '2024-01-01' },
//     { orderId: 2, orderDate: '2024-01-02' }
//   ]
// }
JavaScript

再帰処理の重要性

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

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

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

// 使用例
const columns = ['user_name', 'user_email', 'order_id'];
const camelColumns = arrayToCamel(columns);
console.log(camelColumns); // ['userName', 'userEmail', 'orderId']
JavaScript

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

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

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

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

よくある間違いと注意点

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

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

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

// 間違った例
let str = 'user_name';
str.replace(/_([a-z])/g, function(match, letter) {
  return letter.toUpperCase();
});
console.log(str); // 依然として 'user_name'

// 正しい例
let str = 'user_name';
str = str.replace(/_([a-z])/g, function(match, letter) {
  return letter.toUpperCase();
});
console.log(str); // 'userName'
JavaScript

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

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

// 単純なパスカルケース変換の問題点
function simplePascalToCamel(str) {
  return str.charAt(0).toLowerCase() + str.slice(1);
}

console.log(simplePascalToCamel('HTMLParser'));  // 'hTMLParser'(不自然)
console.log(simplePascalToCamel('URL'));         // 'uRL'(不自然)

// 対策:連続する大文字を適切に処理
function advancedPascalToCamel(str) {
  return str.replace(/^([A-Z]+)([A-Z][a-z])/, function(match, p1, p2) {
    return p1.toLowerCase() + p2;
  }).replace(/^([A-Z])/, function(match) {
    return match.toLowerCase();
  });
}

console.log(advancedPascalToCamel('HTMLParser'));  // 'htmlParser'(自然)
console.log(advancedPascalToCamel('URL'));         // 'url'(自然)
JavaScript

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

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

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

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

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

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

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

const CaseConverter = {
  /**
   * スネークケースをキャメルケースに変換
   * @param {string} str - 変換対象の文字列
   * @returns {string} キャメルケースに変換された文字列
   */
  snakeToCamel: function(str) {
    if (!str || typeof str !== 'string') return '';
    return str.replace(/_([a-z])/g, function(match, letter) {
      return letter.toUpperCase();
    });
  },

  /**
   * パスカルケースをキャメルケースに変換
   * @param {string} str - 変換対象の文字列
   * @returns {string} キャメルケースに変換された文字列
   */
  pascalToCamel: function(str) {
    if (!str || typeof str !== 'string') return '';
    return str.charAt(0).toLowerCase() + str.slice(1);
  },

  /**
   * ケバブケースをキャメルケースに変換
   * @param {string} str - 変換対象の文字列
   * @returns {string} キャメルケースに変換された文字列
   */
  kebabToCamel: function(str) {
    if (!str || typeof str !== 'string') return '';
    return str.replace(/-([a-z])/g, function(match, letter) {
      return letter.toUpperCase();
    });
  },

  /**
   * 任意の文字列をキャメルケースに変換(汎用版)
   * @param {string} str - 変換対象の文字列
   * @returns {string} キャメルケースに変換された文字列
   */
  toCamelCase: function(str) {
    if (!str || typeof str !== 'string') return '';
    
    const words = str.match(/[A-Z]{2,}(?![a-z])|[A-Z]?[a-z]+|[A-Z]|[0-9]+/g);
    if (!words) return str;
    
    return words.map((word, index) => {
      if (index === 0) {
        return word.toLowerCase();
      }
      return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
    }).join('');
  },

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

// 使用例
console.log(CaseConverter.snakeToCamel('user_name'));           // 'userName'
console.log(CaseConverter.pascalToCamel('UserName'));           // 'userName'
console.log(CaseConverter.kebabToCamel('user-name'));           // 'userName'
console.log(CaseConverter.toCamelCase('user_name'));            // 'userName'
console.log(CaseConverter.toCamelCase('UserName'));             // 'userName'
console.log(CaseConverter.toCamelCase('user-name'));            // 'userName'

// オブジェクトの変換例
const data = { user_name: 'Taro', address_info: { zip_code: '123' } };
console.log(CaseConverter.objectKeysToCamel(data));
// { userName: 'Taro', addressInfo: { zipCode: '123' } }
JavaScript

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

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

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

まとめ

キャメルケースへの変換は、データの受け渡しやコードの統一性を保つ上で非常に重要な処理です。今回紹介したように、スネークケース・パスカルケース・ケバブケースという異なる形式から、一貫したキャメルケースへの変換を実現できます。

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

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

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