PHP 백엔드에서 데이터베이스 쿼리를 실행한 후 그 결과를 JSON API로 클라이언트에 전달할 때, DB의 snake_case 필드명을 그대로 노출해서 프론트엔드 개발자에게 지적을 받은 적이 있을 것이다. 다만 데이터가 중첩된 다차원 배열 구조일 때 모든 키 값을 손수 camelCase로 변경하는 유틸리티 함수를 제대로 작성해두지 않아 foreach 루프를 지저분하게 중첩해서 쓰는 경우가 많다.
이번에는 DB 데이터를 REST API 표준 컨벤션에 맞게 자동으로 변환해주는 array_keys_to_camel() 서포트 함수의 원리와 구현 방법, 그리고 실제 프로젝트 적용법까지 완벽하게 정리해서 소개하겠다.
데이터베이스의 컬럼 명명 규칙은 표준적으로 스네이크 케이스(snake_case)를 사용한다. 반면 JavaScript, TypeScript, Swift, Kotlin 등 현대적인 프론트엔드 및 모바일 앱 생태계에서는 캐멀 케이스(camelCase)를 기본 변환 규칙으로 채택하고 있다.
백엔드 API가 user_id나 created_at 같은 키를 그대로 리턴하면 프론트엔드 애플리케이션에서는 일관성이 깨지거나 타입을 정의할 때 혼선이 발생한다.
| 명명 규칙 (Naming Convention) | 예시 (Example) | 주요 사용 환경 |
|---|---|---|
| snake_case | user_profile_img | MySQL/PostgreSQL DB 컬럼, RDBMS 기본 규칙 |
| camelCase | userProfileImg | JavaScript, JSON API response 표준, Swift/Kotlin |
| PascalCase | UserProfileImg | 클래스명, React/Vue 컴포넌트명 |
단순히 1차원 배열만 변환하는 foreach 루프는 중첩된 배열(예: 연관된 주문 내역, 프로필 정보 등)을 만났을 때 내부 키를 변환하지 못하는 치명적인 한계가 있다. 실무에서는 연관 관계가 매핑된 2차원, 3차원 배열을 다루는 일이 흔하므로, 반드시 재귀 호출(Recursive Traversal) 방식을 도입해야 한다.
이제 실무에서 즉시 활용 가능한 서포트 함수 코드와 흔히 범하는 잘못된 작성 예시를 비교해보자.
✗ 잘못된 코드: 1차원 배열의 키만 변환하여 중첩된 하위 배열의 키는 여전히 snake_case로 남아있는 문제 발생
function array_keys_to_camel_bad(array $array): array {
$result = [];
foreach ($array as $key => $value) {
// 단층 구조의 키만 변환
$camelKey = lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', $key))));
$result[$camelKey] = $value; // $value가 배열이어도 내부 키는 변환되지 않음!
}
return $result;
}
✓ 올바른 코드: 재귀 호출을 적용하여 다차원 중첩 배열과 인덱스 배열(리스트)까지 안전하게 camelCase로 변환
/**
* 배열의 모든 키(snake_case)를 camelCase로 재귀 변환하는 서포트 함수
*
* @param array $array 변환할 배열
* @return array camelCase로 변환된 배열
*/
function array_keys_to_camel(array $array): array {
$result = [];
foreach ($array as $key => $value) {
// 1. 키 변환: 문자열 키인 경우 snake_case -> camelCase 변환
$camelKey = $key;
if (is_string($key)) {
$camelKey = lcfirst(preg_replace_callback('/_([a-z0-9])/', function ($matches) {
return strtoupper($matches[1]);
}, $key));
}
// 2. 값 변환: 값이 배열인 경우 재귀적으로 함수 호출
if (is_array($value)) {
$result[$camelKey] = array_keys_to_camel($value);
} else {
$result[$camelKey] = $value;
}
}
return $result;
}
아래는 DB에서 읽어온 중첩된 사용자 데이터를 서포트 함수에 전달하여 변환된 결과를 확인하는 예제다.
// DB에서 조회한 원본 데이터 예시 (snake_case)
$dbData = [
'user_id' => 1042,
'user_name' => '홍길동',
'account_info' => [
'bank_code' => '004',
'account_number' => '123-456-7890'
],
'order_history' => [
[
'order_id' => 'ORD-2023-001',
'product_name' => '무선 마우스',
'item_price' => 25000
]
]
];
// 서포트 함수 적용
$convertedData = array_keys_to_camel($dbData);
// JSON 형태로 출력
echo json_encode($convertedData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);{
"userId": 1042,
"userName": "홍길동",
"accountInfo": {
"bankCode": "004",
"accountNumber": "123-456-7890"
},
"orderHistory": [
{
"orderId": "ORD-2023-001",
"productName": "무선 마우스",
"itemPrice": 25000
}
]
}
다만 대부분의 개발자들은 이 서포트 함수를 만들 때 순서가 있는 리스트 배열(Indexed Array)의 인덱스 키까지 변환하려고 하거나, 특정 특수문자가 들어간 키에서 정규식이 깨지는 문제를 간과하곤 한다.
- 순수 인덱스 배열(Indexed Array) 처리: [0, 1, 2] 형태의 숫자 키를 가진 리스트 배열의 경우, 숫자로 된 키는 변경하지 않고 내부 값(배열)만 재귀적으로 순회해야 데이터 구조가 유지된다.
- 특수문자가 포함된 키: 언더바(_) 외에 하이픈(-)이나 공백이 포함된 인풋 데이터가 들어올 수 있다면 정규식 패턴을
/[_-]([a-z0-9])/i형태로 확장하는 것이 안전하다. - 대용량 데이터 성능 고려: 수만 건 이상의 대용량 데이터셋을 매 요청마다 실시간 변환하면 연산 비용이 증가할 수 있다. 필요에 따라 DB 쿼리 레벨에서 앨리어스(AS)를 쓰거나 Redis 캐싱 응답 전 단계에서 처리하는 것을 추천한다.
PHP에서 DB 데이터와 API 응답 포맷을 일관되게 정형화하는 작업은 프론트엔드와의 협업 효율성을 대폭 높이는 핵심적인 과정이다. 작은 서포트 함수 하나를 유틸리티로 만들어두는 개발 습관이 모여서 깔끔하고 유지보수하기 쉬운 API 아키텍처를 만든다는 점을 잊지 말자. 이 글의 array_keys_to_camel() 서포트 함수 코드를 참고해 작성 중인 API 응답 래퍼(Wrapper)나 헬퍼 클래스에 적용해 보면, 프론트엔드 개발자가 바로 사용할 수 있는 깔끔한 JSON API 응답을 완성할 수 있을 것이다.