業務・実務で使えるJavaScriptユーティリティ:snake_caseに変換する方法
プログラミングの現場では、「UserName」のようなパスカルケースや「userName」のようなキャメルケースを、「user_name」のようなスネークケースに変換する必要が頻繁に生じます。今回は、様々な形式の文字列をスネークケースに統一するユーティリティ関数について、ステップバイステップで解説します。
なぜsnake_caseに変換する必要があるのか
実務では、データベースのカラム名、ファイル名、定数名など、特定の識別子にスネークケースを使用する場面が多くあります。特にSQLのデータベースでは、カラム名やテーブル名にスネークケースを採用することが一般的です。また、環境変数や設定ファイルのキー名としても、スネークケースが広く使われています。
例えば、JavaScriptのコード内で「userName」という変数を使用している場合、データベースに保存する際には「user_name」というカラム名に変換する必要があります。また、APIのレスポンスで受け取った「UserName」を、システム間連携用のフォーマット「user_name」に変換したい場合などが典型的です。
具体的な使用場面
- JavaScriptの変数名をデータベースのカラム名に変換(例:「userName」→「user_name」)
- APIレスポンスのフィールド名を統一(例:「UserName」→「user_name」)
- ファイル名やディレクトリ名の生成(例:「UserProfile」→「user_profile」)
- 環境変数名の生成(例:「apiKey」→「api_key」)
- 定数名の生成(例:「maxValue」→「max_value」)
基本的な実装方法
スネークケースへの変換は、主に以下の3つのパターンに対応する必要があります。それぞれのパターンを段階的に実装していきましょう。
パターン1:キャメルケースからスネークケースへの変換
大文字で区切られた文字列を、アンダースコア(_)で区切り、すべて小文字に変換する方法です。
// キャメルケースをスネークケースに変換する関数
function camelToSnake(str) {
// 空文字やundefinedの場合はそのまま返す(エラー防止)
if (!str) return str;
// 大文字の前にアンダースコアを挿入し、すべて小文字に変換
return str
.replace(/([A-Z])/g, '_$1') // 大文字の前に_を追加
.toLowerCase() // すべて小文字に変換
.replace(/^_/, ''); // 先頭のアンダースコアを削除(最初の文字が大文字の場合)
}
// 使用例
console.log(camelToSnake('userName')); // 'user_name'
console.log(camelToSnake('userEmail')); // 'user_email'
console.log(camelToSnake('orderId')); // 'order_id'
console.log(camelToSnake('user')); // 'user'(変換不要な場合はそのまま)JavaScriptコードの解説
この関数は3つのステップで動作します。
- アンダースコアの挿入:
replace(/([A-Z])/g, '_$1')で、すべての大文字の前にアンダースコアを挿入します。$1はキャプチャした大文字を表します。 - 小文字への変換:
toLowerCase()で文字列全体を小文字に変換します。 - 先頭のアンダースコア削除:
replace(/^_/, '')で、もし先頭にアンダースコアがあれば削除します。これは「UserName」のようなパスカルケースに対応するためです。
パターン2:パスカルケースからスネークケースへの変換
キャメルケースとほぼ同様ですが、先頭が大文字の場合に対応する必要があります。
// パスカルケースをスネークケースに変換する関数
function pascalToSnake(str) {
// 空文字やundefinedの場合はそのまま返す
if (!str) return str;
// 先頭の大文字を小文字に変換し、その後キャメルケースと同じ処理
return str
.replace(/([A-Z])/g, '_$1') // 大文字の前に_を追加
.toLowerCase() // すべて小文字に変換
.replace(/^_/, ''); // 先頭のアンダースコアを削除
}
// 使用例
console.log(pascalToSnake('UserName')); // 'user_name'
console.log(pascalToSnake('UserEmail')); // 'user_email'
console.log(pascalToSnake('OrderId')); // 'order_id'JavaScript連続した大文字への対応
「HTMLParser」のような略語を含む文字列の場合、単純な変換では「h_t_m_l_parser」のような不自然な結果になります。より洗練された変換が必要な場合は、以下のような対応を検討してください。
// より洗練されたスネークケース変換(連続大文字への配慮版)
function pascalToSnakeAdvanced(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(pascalToSnakeAdvanced('UserName')); // 'user_name'
console.log(pascalToSnakeAdvanced('HTMLParser')); // 'html_parser'(自然な結果)
console.log(pascalToSnakeAdvanced('URL')); // 'url'(すべて大文字の場合)JavaScriptパターン3:ケバブケースからスネークケースへの変換
ハイフン(-)で区切られた文字列を、アンダースコア(_)に置き換え、すべて小文字に変換する方法です。
// ケバブケースをスネークケースに変換する関数
function kebabToSnake(str) {
// 空文字やundefinedの場合はそのまま返す
if (!str) return str;
// ハイフンをアンダースコアに置き換え、小文字に変換
return str
.replace(/-/g, '_') // ハイフンをアンダースコアに置き換え
.toLowerCase(); // すべて小文字に変換
}
// 使用例
console.log(kebabToSnake('user-name')); // 'user_name'
console.log(kebabToSnake('user-email')); // 'user_email'
console.log(kebabToSnake('order-id')); // 'order_id'JavaScript汎用的なsnake_case変換関数の実装
実務では、どの形式で入力されるか分からない場合があります。そこで、キャメルケース・パスカルケース・ケバブケースのいずれにも対応できる汎用的な関数を作成しましょう。
統合版スネークケース変換関数
/**
* 任意の文字列をスネークケースに変換する汎用関数
* @param {string} str - 変換対象の文字列
* @returns {string} スネークケースに変換された文字列
*/
function toSnakeCase(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(toSnakeCase('userName')); // 'user_name'
console.log(toSnakeCase('UserName')); // 'user_name'
console.log(toSnakeCase('user-name')); // 'user_name'
console.log(toSnakeCase('user name')); // 'user_name'
console.log(toSnakeCase('USER_NAME')); // 'user_name'
console.log(toSnakeCase('user_name')); // 'user_name'(既にスネークケース)
console.log(toSnakeCase('HTMLParser')); // 'html_parser'
console.log(toSnakeCase('userId123')); // 'user_id_123'
console.log(toSnakeCase(' user name ')); // 'user_name'(スペースも処理)JavaScriptコードの解説
この関数は複数の正規表現を順番に適用して、文字列を正規化していきます。
- 連続大文字の処理:
([A-Z]+)([A-Z][a-z])で、「HTMLParser」のような連続する大文字を適切に区切ります。 - キャメルケースの処理:
([a-z\d])([A-Z])で、「userName」のような小文字と大文字の区切りを処理します。 - ハイフンとスペースの処理:
[-\s]+で、ハイフンやスペースをアンダースコアに置き換えます。 - アンダースコアの正規化:連続するアンダースコアを1つにまとめ、先頭と末尾のアンダースコアを削除します。
- 小文字への変換:最後にすべて小文字に変換します。
重要な注意点:正規表現の順序
上記の正規表現は順序が重要です。連続大文字の処理を先に行わないと、「HTMLParser」が「h_t_m_l_parser」のような不自然な結果になってしまいます。正規表現を組み合わせる場合は、処理の順序を慎重に検討してください。
より実践的な応用パターン
実際の業務では、単一の文字列だけでなく、オブジェクトのキーや配列の要素を一括で変換する場面が多くあります。
1. オブジェクトのキーを一括変換
APIから受け取ったオブジェクトのすべてのキーをスネークケースに変換したい場合、以下のような実装が有効です。
/**
* オブジェクトのキーをすべてスネークケースに変換
* @param {Object} obj - 変換対象のオブジェクト
* @returns {Object} キーがスネークケースに変換された新しいオブジェクト
*/
function objectKeysToSnake(obj) {
// オブジェクトでない場合はそのまま返す
if (typeof obj !== 'object' || obj === null) {
return obj;
}
// 配列の場合は各要素に再帰的に適用
if (Array.isArray(obj)) {
return obj.map(item => objectKeysToSnake(item));
}
// オブジェクトの各キーを変換
return Object.keys(obj).reduce((acc, key) => {
// キーをスネークケースに変換
const snakeKey = toSnakeCase(key);
// 値もオブジェクトの場合は再帰的に変換
acc[snakeKey] = objectKeysToSnake(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 snakeCased = objectKeysToSnake(apiResponse);
console.log(snakeCased);
// 結果:
// {
// 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 arrayToSnake(arr) {
if (!Array.isArray(arr)) return arr;
return arr.map(item => {
if (typeof item === 'string') {
return toSnakeCase(item);
}
return item; // 文字列でない場合はそのまま
});
}
// 使用例
const columns = ['userName', 'userEmail', 'orderId'];
const snakeColumns = arrayToSnake(columns);
console.log(snakeColumns); // ['user_name', 'user_email', 'order_id']JavaScript3. 安全な文字列変換(null・undefined対策)
実務では、予期せぬタイミングでnullやundefinedが渡されることがあります。より堅牢な関数を作成しましょう。
// 型チェックを厳密に行う安全な関数
function safeToSnake(str) {
// 文字列型でない、または空文字の場合は空文字を返す
if (typeof str !== 'string' || str.length === 0) {
return '';
}
// 前後のスペースを除去してから変換
const trimmed = str.trim();
return toSnakeCase(trimmed);
}
// 使用例
console.log(safeToSnake(' userName ')); // 'user_name'(スペースも除去)
console.log(safeToSnake(null)); // ''(エラーにならない)
console.log(safeToSnake(123)); // ''(数値でもエラーにならない)
console.log(safeToSnake(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'JavaScript2. 連続した大文字の扱い
「HTMLParser」や「URL」のような連続した大文字を含む文字列は、単純な変換では意図しない結果になることがあります。以下の例を確認してください。
// 単純な変換の問題点
function simpleCamelToSnake(str) {
return str.replace(/([A-Z])/g, '_$1').toLowerCase().replace(/^_/, '');
}
console.log(simpleCamelToSnake('userName')); // 'user_name'(問題なし)
console.log(simpleCamelToSnake('HTMLParser')); // 'h_t_m_l_parser'(不自然)
// 対策:連続する大文字を適切に処理
function advancedCamelToSnake(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(advancedCamelToSnake('userName')); // 'user_name'(自然)
console.log(advancedCamelToSnake('HTMLParser')); // 'html_parser'(自然)JavaScript3. 日本語を含む文字列への対応
日本語には大文字・小文字の概念がないため、日本語を含む文字列の場合は注意が必要です。以下のコードを確認してください。
// 日本語を含む場合
console.log(toSnakeCase('ユーザー名')); // 'ユーザー名'(変化なし)
console.log(toSnakeCase('userName名前')); // 'user_name名前'(日本語はそのまま)
console.log(toSnakeCase('user_name_テスト')); // 'user_name_テスト'(日本語はそのまま)JavaScript日本語を含む場合でも、基本的には区切り文字(大文字やハイフン)で区切られた部分が正しく処理されます。ただし、日本語同士が連続している場合は、単語の区切りとして認識されないため、意図した結果にならないことがあります。
実務で使えるテンプレート集
コピー&ペーストですぐに使える形でまとめました。プロジェクトの共通ユーティリティファイルに保存しておくと便利です。
/**
* 文字列ケース変換ユーティリティ集
* 業務・実務で使用する文字列操作の関数群
*/
const CaseConverter = {
/**
* キャメルケースをスネークケースに変換
* @param {string} str - 変換対象の文字列
* @returns {string} スネークケースに変換された文字列
*/
camelToSnake: function(str) {
if (!str || typeof str !== 'string') return '';
return str
.replace(/([A-Z])/g, '_$1')
.toLowerCase()
.replace(/^_/, '');
},
/**
* パスカルケースをスネークケースに変換
* @param {string} str - 変換対象の文字列
* @returns {string} スネークケースに変換された文字列
*/
pascalToSnake: function(str) {
if (!str || typeof str !== 'string') return '';
return str
.replace(/([A-Z])/g, '_$1')
.toLowerCase()
.replace(/^_/, '');
},
/**
* ケバブケースをスネークケースに変換
* @param {string} str - 変換対象の文字列
* @returns {string} スネークケースに変換された文字列
*/
kebabToSnake: function(str) {
if (!str || typeof str !== 'string') return '';
return str
.replace(/-/g, '_')
.toLowerCase();
},
/**
* 任意の文字列をスネークケースに変換(汎用版)
* @param {string} str - 変換対象の文字列
* @returns {string} スネークケースに変換された文字列
*/
toSnakeCase: 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} キーがスネークケースに変換された新しいオブジェクト
*/
objectKeysToSnake: function(obj) {
if (typeof obj !== 'object' || obj === null) return obj;
if (Array.isArray(obj)) {
return obj.map(item => this.objectKeysToSnake(item));
}
return Object.keys(obj).reduce((acc, key) => {
const snakeKey = this.toSnakeCase(key);
acc[snakeKey] = this.objectKeysToSnake(obj[key]);
return acc;
}, {});
}
};
// 使用例
console.log(CaseConverter.camelToSnake('userName')); // 'user_name'
console.log(CaseConverter.pascalToSnake('UserName')); // 'user_name'
console.log(CaseConverter.kebabToSnake('user-name')); // 'user_name'
console.log(CaseConverter.toSnakeCase('userName')); // 'user_name'
console.log(CaseConverter.toSnakeCase('UserName')); // 'user_name'
console.log(CaseConverter.toSnakeCase('user-name')); // 'user_name'
// オブジェクトの変換例
const data = { userName: 'Taro', addressInfo: { zipCode: '123' } };
console.log(CaseConverter.objectKeysToSnake(data));
// { user_name: 'Taro', address_info: { zip_code: '123' } }JavaScriptオブジェクト化するメリット
関数をオブジェクト(CaseConverter)にまとめることで、以下のような利点があります。
- 名前空間の汚染を防ぐ:グローバル変数と競合しにくくなります。
- コードの見通しが良くなる:
CaseConverter.camelToSnake()と書くことで、文字列のケース変換であることが明確になります。 - 保守性が向上する:関連する関数が一箇所に集約されるため、修正や追加が容易になります。
- thisによる相互参照:
objectKeysToSnake内でthis.toSnakeCaseを呼び出すことで、同じオブジェクト内の他のメソッドを利用できます。
まとめ
スネークケースへの変換は、データベース連携やシステム間連携、ファイル名生成など、実務で非常に重要な処理です。今回紹介したように、キャメルケース・パスカルケース・ケバブケースという異なる形式から、一貫したスネークケースへの変換を実現できます。
特に重要なのは、「入力値の検証を必ず行う」こと、「文字列は変更不可であることを理解する」こと、そして「連続した大文字や日本語などの特殊なケースへの配慮」の3点です。これらを押さえておけば、実務で発生する様々なケースに対応できるようになります。
まずは基本形のcamelToSnakeやpascalToSnakeから使い始め、必要に応じて汎用的なtoSnakeCaseやオブジェクト一括変換に拡張していくことをお勧めします。チームで共有するユーティリティとして整備することで、コードの一貫性と生産性が大幅に向上するはずです。
