PHP에서 데이터베이스나 배열의 한글 데이터를 JSON으로 변환할 때 이상한 문자열로 깨져나오는 현상을 경험해봤을까? 대부분의 개발자들은 json_encode()를 쓸 때 기본값만 사용하다가 한글이 유니코드 이스케이프 시퀀스(uXXXX 형태)로 나타나는 문제를 마주친다. 다만 이게 단순히 '표시 문제'인지 '실제 데이터 손상'인지, 어디서부터 인코딩을 맞춰야 하는지 정확히 아는 개발자는 많지 않다. 이번에는 json_encode() 한글 깨짐의 정확한 원인, 데이터베이스부터 브라우저까지 전체 흐름에서의 인코딩 검증, 그리고 완벽한 해결책을 실전 코드로 정리해서 소개하겠다.
json_encode()는 PHP 배열이나 객체를 JSON 문자열로 변환한다. 문제는 기본값으로 실행하면 한글을 유니코드 이스케이프 시퀀스로 변환해버린다는 것이다. 이건 JSON 표준상 문제가 아니지만, 실시간 데이터 전송에서는 불필요한 용량 증가와 가독성 저하를 초래한다.
예를 들어, 사용자 정보 배열을 JSON으로 변환하면:
$user = array(
'name' => '홍길동',
'email' => 'hong@example.com'
);
echo json_encode($user);
// 출력: {"name":"ud64duae38ub3d9","email":"hong@example.com"}
보면 'name' 값의 한글이 uXXXX 형태로 인코딩된다. 브라우저나 모바일 앱에서 수신하면 자동으로 디코딩되지만, 문자열 길이가 커지고 데이터베이스 저장 시 호환성 문제가 생길 수 있다.
가장 간단한 해결책은 json_encode()의 두 번째 인자에 JSON_UNESCAPED_UNICODE 옵션을 추가하는 것이다. 이 옵션은 PHP 5.4.0 이상에서 사용 가능하다.
| 옵션 | 설명 | PHP 버전 |
|---|---|---|
| JSON_UNESCAPED_UNICODE | 한글/CJK 문자를 유니코드 이스케이프하지 않음 | 5.4.0+ |
| JSON_UNESCAPED_SLASHES | 슬래시(/)를 이스케이프하지 않음 | 5.4.0+ |
| JSON_PRETTY_PRINT | 가독성 좋게 들여쓰기 형식으로 출력 | 5.4.0+ |
$user = array('name' => '홍길동', 'email' => 'hong@example.com');
echo json_encode($user);
// 출력: {"name":"ud64duae38ub3d9","email":"hong@example.com"}
// 한글이 유니코드 이스케이프로 변환됨
$user = array('name' => '홍길동', 'email' => 'hong@example.com');
echo json_encode($user, JSON_UNESCAPED_UNICODE);
// 출력: {"name":"홍길동","email":"hong@example.com"}
// 한글이 그대로 유지됨
json_encode() 옵션만으로는 부족하다. 데이터베이스 단계에서 한글이 올바르게 저장/조회되지 않으면 JSON도 깨진다. MySQL에서 한글을 제대로 처리하려면 데이터베이스와 테이블, 컬럼 단위로 utf8mb4 문자셋을 확인해야 한다.
-- 데이터베이스 문자셋 확인
SHOW CREATE DATABASE your_database;
-- 테이블 문자셋 확인
SHOW CREATE TABLE your_table;
-- 컬럼 문자셋 확인
SHOW FULL COLUMNS FROM your_table;
utf8이 아닌 utf8mb4를 사용해야 이모지와 특수문자까지 안전하게 저장된다.
-- 데이터베이스 생성 시
CREATE DATABASE your_database CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 기존 데이터베이스 변경
ALTER DATABASE your_database CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 테이블 변경
ALTER TABLE your_table CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
데이터베이스 설정만으로는 부족하다. PHP 코드에서 MySQL에 연결할 때도 문자셋을 명시해야 한다. PDO를 사용할 때는 DSN에 charset을 추가해야 한다.
$pdo = new PDO('mysql:host=localhost;dbname=your_database', 'user', 'password');
// 문자셋을 지정하지 않아 기본값(latin1)이 사용될 수 있음
$pdo = new PDO(
'mysql:host=localhost;dbname=your_database;charset=utf8mb4',
'user',
'password',
array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION)
);
데이터베이스에서 조회한 한글 데이터를 JSON으로 반환하는 완벽한 예제를 보자.
<?php
// 1. PDO 연결 (UTF8MB4 문자셋 지정)
$pdo = new PDO(
'mysql:host=localhost;dbname=shop;charset=utf8mb4',
'root',
'password',
array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION)
);
// 2. 한글 데이터가 포함된 쿼리
$stmt = $pdo->prepare('SELECT id, name, description FROM products WHERE category = ?');
$stmt->execute(['의류']);
$products = $stmt->fetchAll(PDO::FETCH_ASSOC);
// 3. JSON 반환 시 JSON_UNESCAPED_UNICODE 옵션 적용
header('Content-Type: application/json; charset=utf-8');
echo json_encode([
'status' => 'success',
'data' => $products
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
?>
출력 결과:
{
"status": "success",
"data": [
{
"id": "1",
"name": "반팔 티셔츠",
"description": "편안한 면 소재, 여름용 추천 상품"
},
{
"id": "2",
"name": "청바지",
"description": "스트레칭 기능 포함, 겨울 따뜻함"
}
]
}
Content-Type 헤더는 echo나 출력 명령보다 반드시 먼저 호출해야 한다. 한 줄이라도 출력되면 header()를 사용할 수 없다.
// ✗ 잘못된 순서
echo 'test';
header('Content-Type: application/json; charset=utf-8');
// Fatal error: Cannot modify header information
// ✓ 올바른 순서
header('Content-Type: application/json; charset=utf-8');
echo json_encode($data, JSON_UNESCAPED_UNICODE);
json_encode()로 이미 JSON 문자열을 만들었는데 다시 json_encode()를 적용하면 문자열이 이중 인코딩된다.
// ✗ 이중 인코딩
$json = json_encode(['name' => '홍길동'], JSON_UNESCAPED_UNICODE);
echo json_encode($json); // "{"name":"홍길동"}"
// ✓ 올바른 방법
$data = ['name' => '홍길동'];
echo json_encode($data, JSON_UNESCAPED_UNICODE);
데이터베이스에서 조회한 데이터도 json_encode() 실패가 발생할 수 있다. 순환 참조나 리소스 타입이 포함되면 false를 반환한다.
// ✗ 에러 처리 없음
$json = json_encode($data, JSON_UNESCAPED_UNICODE);
// 실패해도 모를 수 있음
// ✓ 에러 검증
$json = json_encode($data, JSON_UNESCAPED_UNICODE);
if ($json === false) {
error_log('JSON 인코딩 실패: ' . json_last_error_msg());
echo json_encode(['error' => 'Data processing failed'], JSON_UNESCAPED_UNICODE);
exit();
}
echo $json;
PHP의 json_encode() 한글 깨짐 문제는 단순히 옵션 하나를 추가하는 것보다 데이터베이스 문자셋부터 브라우저 수신까지 전체 흐름을 이해해야 한다. 작은 한글 한 글자의 제대로 된 인코딩이 모여서 안정적인 국제화 서비스를 만든다는 점을 잊지 말자. 이 글의 5단계 실전 예제를 참고해 다음 체크리스트를 따르면, 앞으로 JSON 데이터 전송에서 한글 문제를 완벽히 해결할 수 있을 것이다.
json_encode() 한글 처리 체크리스트:
- ✓ 데이터베이스: utf8mb4 문자셋 사용
- ✓ 테이블/컬럼: utf8mb4 CHARACTER SET 확인
- ✓ PDO 연결: charset=utf8mb4 DSN 명시
- ✓ json_encode(): JSON_UNESCAPED_UNICODE 옵션 적용
- ✓ 헤더: Content-Type: application/json; charset=utf-8 설정
- ✓ 에러 처리: json_last_error_msg()로 실패 감지