PHP에서 JSON 데이터를 처리할 때 발생하는 파싱 오류와 해결 방법을 알아보겠습니다.
json_decode() returns null
JSON Error: Syntax error, malformed JSON
JSON Error: Control character error, possibly incorrectly encoded
<?php
class JsonParser {
public static function decode($json, $assoc = true) {
// 빈 값 체크
if (empty($json)) {
throw new InvalidArgumentException('JSON 문자열이 비어있습니다.');
}
// JSON 디코딩
$data = json_decode($json, $assoc);
// 오류 체크
$error = json_last_error();
if ($error !== JSON_ERROR_NONE) {
throw new JsonException(self::getErrorMessage($error), $error);
}
return $data;
}
public static function encode($data, $options = 0) {
$json = json_encode($data, $options | JSON_UNESCAPED_UNICODE);
if ($json === false) {
$error = json_last_error();
throw new JsonException(self::getErrorMessage($error), $error);
}
return $json;
}
private static function getErrorMessage($error) {
$messages = [
JSON_ERROR_NONE => 'No error',
JSON_ERROR_DEPTH => 'Maximum stack depth exceeded',
JSON_ERROR_STATE_MISMATCH => 'State mismatch (invalid or malformed JSON)',
JSON_ERROR_CTRL_CHAR => 'Control character error, possibly incorrectly encoded',
JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON',
JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded',
JSON_ERROR_RECURSION => 'One or more recursive references in the value to be encoded',
JSON_ERROR_INF_OR_NAN => 'One or more NAN or INF values in the value to be encoded',
JSON_ERROR_UNSUPPORTED_TYPE => 'A value of a type that cannot be encoded was given'
];
return $messages[$error] ?? 'Unknown JSON error';
}
}
?>
<?php
class JsonValidator {
public static function sanitize($input) {
// BOM 제거
$input = self::removeBOM($input);
// 제어 문자 제거
$input = self::removeControlCharacters($input);
// 잘못된 인코딩 수정
$input = self::fixEncoding($input);
return trim($input);
}
private static function removeBOM($input) {
// UTF-8 BOM 제거
if (substr($input, 0, 3) === "\xEF\xBB\xBF") {
$input = substr($input, 3);
}
return $input;
}
private static function removeControlCharacters($input) {
// 제어 문자 제거 (탭, 개행 제외)
return preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', $input);
}
private static function fixEncoding($input) {
// UTF-8로 변환
if (!mb_check_encoding($input, 'UTF-8')) {
$input = mb_convert_encoding($input, 'UTF-8', 'auto');
}
return $input;
}
public static function isValid($json) {
json_decode($json);
return json_last_error() === JSON_ERROR_NONE;
}
}
?>
<?php
class ApiJsonHandler {
public static function getRequestData() {
$contentType = $_SERVER['CONTENT_TYPE'] ?? '';
if (strpos($contentType, 'application/json') === false) {
throw new InvalidArgumentException('Content-Type이 application/json이 아닙니다.');
}
$rawInput = file_get_contents('php://input');
if (empty($rawInput)) {
return [];
}
try {
// JSON 데이터 정리
$cleanInput = JsonValidator::sanitize($rawInput);
// JSON 파싱
$data = JsonParser::decode($cleanInput);
return $data;
} catch (Exception $e) {
self::logJsonError($rawInput, $e->getMessage());
throw new InvalidArgumentException('JSON 파싱 실패: ' . $e->getMessage());
}
}
public static function sendResponse($data, $statusCode = 200) {
http_response_code($statusCode);
header('Content-Type: application/json; charset=utf-8');
try {
echo JsonParser::encode($data, JSON_PRETTY_PRINT);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => 'Response encoding failed']);
}
}
private static function logJsonError($input, $error) {
error_log("JSON 파싱 오류: $error");
error_log("입력 데이터: " . substr($input, 0, 500));
}
}
?>
<?php
class JsonFileHandler {
public static function readFile($filepath) {
if (!file_exists($filepath)) {
throw new InvalidArgumentException("파일을 찾을 수 없습니다: $filepath");
}
$content = file_get_contents($filepath);
if ($content === false) {
throw new RuntimeException("파일 읽기 실패: $filepath");
}
try {
$cleanContent = JsonValidator::sanitize($content);
return JsonParser::decode($cleanContent);
} catch (Exception $e) {
throw new RuntimeException("JSON 파일 파싱 실패 ($filepath): " . $e->getMessage());
}
}
public static function writeFile($filepath, $data) {
try {
$json = JsonParser::encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
$result = file_put_contents($filepath, $json, LOCK_EX);
if ($result === false) {
throw new RuntimeException("파일 쓰기 실패: $filepath");
}
return $result;
} catch (Exception $e) {
throw new RuntimeException("JSON 파일 저장 실패 ($filepath): " . $e->getMessage());
}
}
}
?>
<?php
class JsonDebugger {
public static function analyze($input) {
echo "=== JSON 분석 결과 ===\n";
echo "원본 길이: " . strlen($input) . "\n";
echo "인코딩: " . mb_detect_encoding($input) . "\n";
// BOM 체크
if (substr($input, 0, 3) === "\xEF\xBB\xBF") {
echo "⚠️ UTF-8 BOM 발견\n";
}
// 제어 문자 체크
if (preg_match('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', $input)) {
echo "⚠️ 제어 문자 발견\n";
}
// JSON 유효성 검사
json_decode($input);
$error = json_last_error();
if ($error === JSON_ERROR_NONE) {
echo "✅ 유효한 JSON\n";
} else {
echo "❌ JSON 오류: " . json_last_error_msg() . "\n";
// 오류 위치 추정
self::findErrorPosition($input);
}
echo "\n=== 처음 100자 ===\n";
echo substr($input, 0, 100) . "\n";
}
private static function findErrorPosition($input) {
$lines = explode("\n", $input);
foreach ($lines as $lineNum => $line) {
json_decode($line);
if (json_last_error() !== JSON_ERROR_NONE) {
echo "문제 라인 " . ($lineNum + 1) . ": $line\n";
break;
}
}
}
}
// 사용 예시
$problematicJson = '{"name": "test", "value": }'; // 잘못된 JSON
JsonDebugger::analyze($problematicJson);
?>
<?php
require_once 'src/json_parser.php';
require_once 'src/json_validator.php';
require_once 'src/api_json_handler.php';
try {
// API 요청 처리
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$requestData = ApiJsonHandler::getRequestData();
// 데이터 처리
$response = [
'status' => 'success',
'received' => $requestData,
'timestamp' => date('Y-m-d H:i:s')
];
ApiJsonHandler::sendResponse($response);
}
} catch (InvalidArgumentException $e) {
ApiJsonHandler::sendResponse([
'status' => 'error',
'message' => $e->getMessage()
], 400);
} catch (Exception $e) {
ApiJsonHandler::sendResponse([
'status' => 'error',
'message' => 'Internal server error'
], 500);
}
?>