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

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

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

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

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

実務では、クラス名やコンストラクタ名、コンポーネント名など、特定の識別子にパスカルケースを使用する場面が多くあります。特にJavaScriptのクラス名やReactのコンポーネント名では、パスカルケースが標準的な命名規則とされています。

例えば、データベースに「user_name」というカラム名で保存されているデータを、JavaScriptのクラス名「UserName」として使用したい場合や、APIから受け取った「userName」をReactコンポーネント名「UserName」に変換したい場合などが典型的です。

具体的な使用場面

  • データベースのテーブル名からクラス名を生成(例:「user_profiles」→「UserProfiles」)
  • APIレスポンスのフィールド名からコンポーネント名を作成(例:「userName」→「UserName」)
  • ファイル名からクラス名を推測(例:「user-service.js」→「UserService」)
  • 動的に型名やインターフェース名を生成(例:「order_history」→「OrderHistory」)

基本的な実装方法

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

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

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

// スネークケースをパスカルケースに変換する関数
function snakeToPascal(str) {
  // 空文字やundefinedの場合はそのまま返す(エラー防止)
  if (!str) return str;
  
  // アンダースコアで分割し、各単語の先頭を大文字にして結合
  return str
    .split('_')           // アンダースコアで配列に分割
    .map(word => {        // 各単語に対して処理
      return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
    })
    .join('');            // 空文字で結合(単語を連結)
}

// 使用例
console.log(snakeToPascal('user_name'));      // 'UserName'
console.log(snakeToPascal('user_email'));     // 'UserEmail'
console.log(snakeToPascal('order_id'));       // 'OrderId'
console.log(snakeToPascal('user'));           // 'User'(単一単語の場合)
JavaScript

コードの解説

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

  1. 文字列の分割:split('_')でアンダースコアを区切り文字として配列に分割します。
  2. 各単語の変換:map()で各単語に対して、先頭文字を大文字、残りを小文字に変換します。
  3. 文字列の結合:join('')で空文字を区切りとして単語を連結し、1つの文字列に戻します。

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

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

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

// 使用例
console.log(camelToPascal('userName'));    // 'UserName'
console.log(camelToPascal('orderId'));     // 'OrderId'
console.log(camelToPascal('user'));        // 'User'
JavaScript

連続した大文字への対応

「htmlParser」のような文字列の場合、単純に先頭を大文字にするだけで問題ありません。しかし、「HTMLParser」のような略語を含む文字列の場合、より洗練された変換が必要になることがあります。

// より洗練されたパスカルケース変換(連続大文字への配慮版)
function camelToPascalAdvanced(str) {
  if (!str) return str;
  
  // 先頭の連続する小文字を大文字に、残りは適切に処理
  return str.replace(/^([a-z]+)([A-Z])/, function(match, p1, p2) {
    return p1.charAt(0).toUpperCase() + p1.slice(1) + p2;
  }).replace(/^([a-z])/, function(match) {
    return match.toUpperCase();
  });
}

// 使用例
console.log(camelToPascalAdvanced('userName'));     // 'UserName'
console.log(camelToPascalAdvanced('htmlParser'));   // 'HtmlParser'(より自然な結果)
console.log(camelToPascalAdvanced('url'));          // 'Url'(すべて小文字の場合)
JavaScript

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

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

// ケバブケースをパスカルケースに変換する関数
function kebabToPascal(str) {
  // 空文字やundefinedの場合はそのまま返す
  if (!str) return str;
  
  // ハイフンで分割し、各単語の先頭を大文字にして結合
  return str
    .split('-')           // ハイフンで配列に分割
    .map(word => {        // 各単語に対して処理
      return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
    })
    .join('');            // 空文字で結合(単語を連結)
}

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

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

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

統合版パスカルケース変換関数

/**
 * 任意の文字列をパスカルケースに変換する汎用関数
 * @param {string} str - 変換対象の文字列
 * @returns {string} パスカルケースに変換された文字列
 */
function toPascalCase(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 => {
    return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
  }).join('');
}

// 使用例
console.log(toPascalCase('user_name'));          // 'UserName'
console.log(toPascalCase('user-name'));          // 'UserName'
console.log(toPascalCase('userName'));           // 'UserName'
console.log(toPascalCase('user name'));          // 'UserName'
console.log(toPascalCase('USER_NAME'));          // 'UserName'
console.log(toPascalCase('UserName'));           // 'UserName'(既にパスカルケース)
console.log(toPascalCase('html_parser'));        // 'HtmlParser'
console.log(toPascalCase('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. パスカルケースへの組み立て:すべての単語の先頭を大文字に、残りを小文字にして連結します。

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

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

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

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

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

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

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

/**
 * オブジェクトのキーをすべてパスカルケースに変換
 * @param {Object} obj - 変換対象のオブジェクト
 * @returns {Object} キーがパスカルケースに変換された新しいオブジェクト
 */
function objectKeysToPascal(obj) {
  // オブジェクトでない場合はそのまま返す
  if (typeof obj !== 'object' || obj === null) {
    return obj;
  }
  
  // 配列の場合は各要素に再帰的に適用
  if (Array.isArray(obj)) {
    return obj.map(item => objectKeysToPascal(item));
  }
  
  // オブジェクトの各キーを変換
  return Object.keys(obj).reduce((acc, key) => {
    // キーをパスカルケースに変換
    const pascalKey = toPascalCase(key);
    // 値もオブジェクトの場合は再帰的に変換
    acc[pascalKey] = objectKeysToPascal(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 pascalCased = objectKeysToPascal(apiResponse);
console.log(pascalCased);
// 結果:
// {
//   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 arrayToPascal(arr) {
  if (!Array.isArray(arr)) return arr;
  
  return arr.map(item => {
    if (typeof item === 'string') {
      return toPascalCase(item);
    }
    return item; // 文字列でない場合はそのまま
  });
}

// 使用例
const columns = ['user_name', 'user_email', 'order_id'];
const pascalColumns = arrayToPascal(columns);
console.log(pascalColumns); // ['UserName', 'UserEmail', 'OrderId']
JavaScript

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

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

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

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

よくある間違いと注意点

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

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

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

// 間違った例
let str = 'user_name';
str.split('_').map(word => word.charAt(0).toUpperCase() + word.slice(1));
console.log(str); // 依然として 'user_name'

// 正しい例
let str = 'user_name';
str = str.split('_').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join('');
console.log(str); // 'UserName'
JavaScript

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

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

// 単純なキャメルケース変換の問題点
function simpleCamelToPascal(str) {
  return str.charAt(0).toUpperCase() + str.slice(1);
}

console.log(simpleCamelToPascal('htmlParser'));  // 'HtmlParser'(問題なし)
console.log(simpleCamelToPascal('HTMLParser'));  // 'HTMLParser'(このまま)

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

console.log(advancedCamelToPascal('htmlParser'));  // 'HtmlParser'(自然)
console.log(advancedCamelToPascal('HTMLParser'));  // 'HTMLParser'(連続大文字は維持)
JavaScript

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

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

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

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

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

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

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

const CaseConverter = {
  /**
   * スネークケースをパスカルケースに変換
   * @param {string} str - 変換対象の文字列
   * @returns {string} パスカルケースに変換された文字列
   */
  snakeToPascal: function(str) {
    if (!str || typeof str !== 'string') return '';
    return str
      .split('_')
      .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
      .join('');
  },

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

  /**
   * ケバブケースをパスカルケースに変換
   * @param {string} str - 変換対象の文字列
   * @returns {string} パスカルケースに変換された文字列
   */
  kebabToPascal: function(str) {
    if (!str || typeof str !== 'string') return '';
    return str
      .split('-')
      .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
      .join('');
  },

  /**
   * 任意の文字列をパスカルケースに変換(汎用版)
   * @param {string} str - 変換対象の文字列
   * @returns {string} パスカルケースに変換された文字列
   */
  toPascalCase: 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 => {
      return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
    }).join('');
  },

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

// 使用例
console.log(CaseConverter.snakeToPascal('user_name'));           // 'UserName'
console.log(CaseConverter.camelToPascal('userName'));            // 'UserName'
console.log(CaseConverter.kebabToPascal('user-name'));           // 'UserName'
console.log(CaseConverter.toPascalCase('user_name'));            // 'UserName'
console.log(CaseConverter.toPascalCase('userName'));             // 'UserName'
console.log(CaseConverter.toPascalCase('user-name'));            // 'UserName'

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

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

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

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

まとめ

パスカルケースへの変換は、クラス名やコンポーネント名の生成、データ構造の統一など、実務で非常に重要な処理です。今回紹介したように、スネークケース・キャメルケース・ケバブケースという異なる形式から、一貫したパスカルケースへの変換を実現できます。

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

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

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