PHP JSON 파싱 오류 해결하기

프리터코더·2025년 5월 25일
0

php 문제 해결

목록 보기
25/79

PHP에서 JSON 데이터를 처리할 때 발생하는 파싱 오류와 해결 방법을 알아보겠습니다.

주요 오류 메시지

json_decode() returns null
JSON Error: Syntax error, malformed JSON
JSON Error: Control character error, possibly incorrectly encoded

원인 분석

  1. 잘못된 JSON 형식: 문법 오류, 따옴표 누락
  2. 인코딩 문제: UTF-8이 아닌 문자 인코딩
  3. 특수 문자: 제어 문자나 이스케이프되지 않은 문자
  4. 데이터 타입 오류: JSON에서 지원하지 않는 데이터 타입

해결책

1. 안전한 JSON 파싱 함수

<?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';
    }
}
?>

2. JSON 데이터 검증 및 정리

<?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;
    }
}
?>

3. API 요청 JSON 처리

<?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));
    }
}
?>

4. 파일에서 JSON 읽기

<?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());
        }
    }
}
?>

5. 디버깅 도구

<?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);
?>

6. 실제 사용 예시

<?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);
}
?>

예방법

  • 입력 데이터 사전 검증 및 정리
  • 적절한 인코딩 설정 (UTF-8)
  • JSON 스키마 검증 도입
  • 에러 로깅 및 모니터링
  • 클라이언트와 서버 간 데이터 형식 명세
profile
일용직 개발자. freetercoder@gmail.com

0개의 댓글