JSON.parse 예외 안전하게 처리하기 — try-catch로 404/무효 JSON 오류 잡기
1. 문제 정의
JSON.parse는 문자열을 JavaScript 객체로 변환하는 내장 함수이지만, 인자로 들어온 문자열이 유효한 JSON이 아니면 예외(SyntaxError)를 던진다.
실무에서는 서버 응답을 JSON.parse로 파싱할 때가 많다. 특히 아래 질문처럼 XMLHttpRequest로 요청을 보냈는데 응답이 404인 경우, 응답 본문이 JSON이 아니기 때문에 파싱이 실패한다.
| |
response에 404 본문(HTML 등)이 들어오면 이 코드는 JSON.parse에서 예외를 던지고, 처리되지 않은 예외(uncaught exception)로 이어져 이후 코드가 실행되지 않는다.
2. 원인 탐구
왜 이런 예외가 발생하는지 이해하기 위해 JSON.parse의 동작을 살펴본다.
| 상황 | response의 실제 내용 | JSON.parse 결과 |
|---|---|---|
| 정상 응답 | {"id": 1, "type": "Foo"} | 객체 반환 (성공) |
| 404 응답 | HTML 또는 빈 문자열 | SyntaxError 예외 발생 |
| 잘못된 문자열 | not json at all | SyntaxError 예외 발생 |
| 빈 문자열 | "" | SyntaxError 예외 발생 |
핵심 요점: JSON.parse는 실패 시 결과를 반환하지 않고 예외를 던진다. 즉 “파싱 실패 여부를 리턴값으로 확인하는” 방식은 불가능하다. 실패는 반드시 예외로만 신호된다.
따라서 404 응답처럼 본문이 JSON이 아닌 경우를 대비하지 않으면 처리되지 않은 예외가 발생한다.
3. 근본 원인 분석
근본 원인은 두 가지가 결합된 것이다.
JSON.parse의 실패 신호 방식 — 파싱 실패는 반환값이 아니라 예외(SyntaxError)로 전달된다. 호출부가 try-catch로 감싸지 않으면 예외가 그대로 상위로 전파되어 크래시를 일으킨다.- 응답 본문이 항상 JSON이라는 보장이 없다 — 404/500 응답은 HTTP 상태 코드가 다를 뿐 아니라 본문 형식도 JSON이 아닐 수 있다(서버 에러 페이지 HTML 등). 요청 성공 여부를
response내용만으로 가정하면 안 된다.
정리하면, “파싱을 시도했는데 그 문자열이 JSON이 아닐 수 있다"는 사실을 전제로 코드를 작성해야 하며, 이를 위해 파싱 실패 경로를 예외 처리로 감싸는 것이 정석이다.
4. 코드 해결책
가장 간단하고 널리 쓰이는 해결책은 JSON.parse를 try-catch 블록으로 감싸는 것이다.
| |
이 패턴의 동작 방식:
response가 비어 있지 않은지 먼저 확인한다 (if (response)).JSON.parse는 try 블록 안에서 실행된다.- 파싱이 성공하면
a에 객체가 할당되고, try 블록을 정상 통과해 이후 코드가 실행된다. - 파싱이 실패하면 throw된 예외가
catch (e)로 잡히고,console.error(e)로 기록한 뒤return하여 크래시 없이 함수를 끝낸다.
파싱된 결과를 사용하는 코드의 올바른 위치
성공한 파싱 결과 a를 사용하는 코드는 반드시 try-catch 뒤(또는 try 블록 안)에 두어야 한다. catch에서 return하지 않으면 a가 undefined인 채로 아래 코드가 실행될 수 있다.
| |
더 엄격한 방식: 파싱 실패를 명시적으로 처리
상황에 따라 404 등을 별도로 분기하고 싶다면, try-catch로 감싸는 동시에 응답 상태 코드를 함께 확인할 수도 있다.
| |
5. 향후 예방 조치
같은 예외를 다시 만나지 않으려면 아래 원칙을 지킨다.
- 무효할 수 있는 JSON에는 반드시 try-catch를 쓴다. 서버 응답처럼 “항상 유효한 JSON"이 보장되지 않는 문자열에는
JSON.parse를 반드시 try-catch로 감싼다. - 파싱 전에 응답 상태/형식을 확인한다. XHR/fetch를 쓴다면 응답이 성공(2xx)이고 콘텐츠 타입이 JSON인지 먼저 확인한 뒤 파싱한다.
- 파싱 결과는 try 블록 안에서만 사용한다. catch에서
return을 빠뜨리면undefined가 아래로 흘러가 2차 오류가 난다. - 오류를 삼키지 말고 기록한다.
catch (e) { /* 빈 블록 */ }은 디버깅을 어렵게 만든다. 최소한console.error(e)처럼 로그를 남긴다. - 에러 처리 영역을 헬퍼로 추출한다. 반복되는 파싱에는 안전한
safeParse같은 헬퍼 함수를 만들어 일관되게 처리한다.
| |