본문으로 건너뛰기
소이툴즈

JSON 파싱 오류 원인별 해결 — 브라우저마다 다른 메시지

크롬(V8)·파이어폭스(SpiderMonkey)·사파리(JavaScriptCore)가 같은 JSON 오류에 내는 실제 메시지를 원인별로 대조하고, 메시지에서 원인으로 역추적하는 법, HTML이 온 경우, 큰 정수가 바뀌는 문제, 오류 문구를 코드에서 다룰 때의 주의점을 정리했습니다.

같은 잘못된 JSON {"a": 1,}를 파싱하면 크롬은 Expected double-quoted property name in JSON at position 8, 파이어폭스는 expected double-quoted property name at line 1 column 9 of the JSON data, 사파리는 JSON Parse error: Property name must be a string literal이라고 말합니다. 원인은 하나(후행 쉼표)인데 문구가 셋이고, 사파리는 위치조차 알려 주지 않습니다. 이 글은 크롬(V8)·파이어폭스(SpiderMonkey)·사파리(JavaScriptCore) 세 엔진에서 실제로 나오는 메시지를 원인별로 나란히 놓고, 메시지만 보고 무엇을 고쳐야 하는지 역추적하는 표를 제공합니다. 메시지는 크로미움 151, 파이어폭스 153, 웹킷 26.5에서 직접 실행해 받아 적은 것입니다.

엔진마다 메시지 형식이 다른 이유

JSON 표준(RFC 8259)은 문법만 정하고 오류 메시지는 정하지 않습니다. 그래서 각 자바스크립트 엔진이 자기 방식대로 문구를 만들고, 브라우저가 바뀌면 같은 코드에서 다른 메시지가 나옵니다. Node.js는 V8을 쓰므로 크롬과 같고, iOS의 모든 브라우저는 애플 정책상 웹킷을 쓰므로 아이폰의 크롬조차 사파리 메시지를 냅니다. 국내 모바일 트래픽의 상당수가 아이폰이라, 오류 메시지를 문자열로 검사하는 코드가 크롬에서만 테스트되었다면 사파리 사용자에게는 영어 원문이 그대로 노출됩니다. JSON 정렬·검사기가 세 엔진의 문구를 모두 인식해 한국어로 바꾸는 이유입니다.

| 엔진 | 브라우저 | 위치 정보 | 접두어 | | ---------------- | ------------------------------- | ---------------------------------- | ------------------- | | V8 | 크롬, 엣지, 웨일, Node.js | at position N (line L column C) | 없음 | | SpiderMonkey | 파이어폭스 | at line L column C of the JSON data | JSON.parse: | | JavaScriptCore | 사파리, iOS의 모든 브라우저 | 없음 | JSON Parse error: |

V8의 position은 0부터 세는 문자 오프셋이고 괄호 안의 line·column은 1부터 셉니다. 파이어폭스는 줄·열만 줍니다. 사파리는 어느 것도 주지 않으므로, 사파리에서 오류가 났다면 위치 대신 메시지의 종류로 원인을 좁혀야 합니다.

원인별 메시지 대조표

| 원인 | 크롬 (V8) | 파이어폭스 (SpiderMonkey) | 사파리 (JavaScriptCore) | | ------------------------------ | ------------------------------------------------------------ | ---------------------------------------------------------- | ------------------------------------------- | | 후행 쉼표 {"a": 1,} | Expected double-quoted property name | expected double-quoted property name | Property name must be a string literal | | 작은따옴표 {'a': 1} | Expected property name or '}' | expected property name or '}' | Single quotes (') are not allowed in JSON | | 따옴표 없는 키 {a: 1} | Expected property name or '}' | expected property name or '}' | Expected '}' | | 주석 // | Expected ',' or '}' after property value | expected ',' or '}' after property value in object | Unrecognized token '/' | | 쉼표 누락 {"a": 1 "b": 2} | Expected ',' or '}' after property value | expected ',' or '}' after property value in object | Expected '}' | | 닫는 괄호 누락 [1, 2 | Expected ',' or ']' after array element | end of data when ',' or ']' was expected | Expected ']' | | 빈 입력 | Unexpected end of JSON input | unexpected end of data | Unexpected EOF | | undefined 값 | Unexpected token 'u', "..." is not valid JSON | unexpected character | Unexpected identifier "undefined" | | HTML이 들어옴 <!DOCTYPE | Unexpected token '<', "<!DOCTYPE "... is not valid JSON | unexpected character at line 1 column 1 | Unrecognized token '<' | | 문자열 안 실제 줄바꿈 | Bad control character in string literal | bad control character in string literal | Unterminated string | | 선행 0 01 | Unexpected number | expected ',' or '}' after property value in object | Expected '}' | | 값이 두 개 {"a":1}{"b":2} | Unexpected non-whitespace character after JSON | unexpected non-whitespace character after JSON data | Unable to parse JSON string | | JSON이 아닌 단어 hello | Unexpected token 'h', "hello" is not valid JSON | unexpected character at line 1 column 1 | Unexpected identifier "hello" |

표에서 보이듯 사파리는 여러 원인을 Expected '}' 하나로 뭉뚱그리고, 파이어폭스는 undefined·HTML·엉뚱한 단어를 모두 unexpected character로 냅니다. 반대로 사파리만 작은따옴표를 콕 집어 알려 주고, 크롬만 문제의 문자를 따옴표에 넣어 보여 줍니다. 세 브라우저 중 어느 하나도 다른 둘보다 일관되게 친절하지는 않습니다.

메시지에서 원인으로 가는 길

"property name" 계열은 키 자리에 큰따옴표 문자열이 아닌 것이 왔다는 뜻입니다. 마지막 항목 뒤의 쉼표, 작은따옴표, 따옴표 없는 키가 여기에 걸립니다. 자바스크립트 콘솔에서 객체를 복사했거나 파이썬 딕셔너리를 그대로 붙여 넣었을 때 가장 흔합니다. 크롬의 position이 여는 중괄호 바로 다음(1)이면 첫 키부터 문제이고, 끝 근처면 후행 쉼표입니다.

Expected ',' or '}' 계열은 값이 끝났는데 다음에 쉼표도 닫는 괄호도 오지 않았다는 뜻입니다. 항목 사이 쉼표 누락, 주석, 16진수 같은 비표준 숫자가 원인입니다. 표시된 위치는 값이 끝난 직후이므로 그 앞 항목을 봐야 합니다.

**end of data, Unexpected EOF, Unexpected end of JSON input**은 입력이 끝났는데 파서가 아직 뭔가를 기다리고 있다는 뜻입니다. 괄호나 따옴표가 안 닫혔거나, 네트워크 응답이 중간에 잘렸거나, 빈 문자열을 파싱한 것입니다. 빈 응답은 서버가 204를 보냈거나 본문이 없는데 무조건 response.json()을 부른 경우가 대부분이라, 응답 코드와 Content-Length를 먼저 확인합니다.

**Unexpected token '<'**은 거의 항상 JSON 대신 HTML이 왔다는 뜻입니다. API 주소가 틀려 404 페이지가 왔거나, 로그인이 풀려 로그인 페이지로 리다이렉트되었거나, 프록시나 방화벽이 차단 페이지를 끼워 넣은 것입니다. 네트워크 탭에서 응답 본문을 열어 보면 바로 드러납니다. 프런트엔드 코드를 고칠 일이 아니라 요청 경로나 인증을 볼 일입니다.

**Bad control characterUnterminated string**은 문자열 안에 실제 줄바꿈이나 탭이 들어 있다는 뜻입니다. JSON 문자열 안의 줄바꿈은 반드시 \n 두 글자로 이스케이프해야 하고, 사용자가 입력한 텍스트를 문자열 연결로 JSON에 끼워 넣을 때 이 문제가 생깁니다. 직접 문자열을 조립하지 말고 언어의 직렬화 함수를 쓰면 사라집니다.

**non-whitespace character after JSON**은 완전한 JSON 뒤에 뭔가가 더 있다는 뜻입니다. 줄마다 객체가 하나씩 있는 로그 파일(JSON Lines)이나 두 응답이 이어 붙은 경우이고, 줄 단위로 나누어 각각 파싱해야 합니다.

오류가 아닌데 값이 바뀌는 경우

파싱은 성공했는데 값이 다르다면 큰 정수를 의심합니다. 자바스크립트 숫자는 2의 53제곱(9,007,199,254,740,992)까지만 정확해서 90071992547409939007199254740992로, 20자리 ID는 뒷자리가 0으로 바뀝니다. 오류가 나지 않으므로 발견이 늦습니다. 서버가 64비트 ID를 숫자로 내보내면 문자열로 바꿔 보내도록 하거나, 파싱 전에 정규식으로 큰 숫자를 따옴표로 감싸야 합니다. 같은 키가 두 번 나오면 오류 없이 마지막 값이 이기고, 같은 유니코드 이스케이프는 한글로 풀리는 것이 정상 동작입니다.

오류 메시지를 코드에서 다룰 때

메시지 문자열을 includes로 검사하는 코드는 브라우저 하나에서만 맞습니다. 사용자에게 보여 줄 문구를 만든다면 세 엔진의 문구를 모두 정규식으로 잡되, 잡히지 않는 메시지는 원문을 그대로 두는 것이 조용히 틀린 안내를 하는 것보다 낫습니다. 위치는 크롬의 position을 줄·열로 환산하고, 파이어폭스의 line·column은 그대로 쓰며, 사파리처럼 위치가 없으면 "1줄 1열"로 꾸미지 말고 위치 표시를 감춰야 합니다. 엔진 버전에 따라 문구가 바뀌기도 하므로(크롬은 2023년에 Unexpected token 계열을 지금 형태로 바꿨습니다) 정규식은 느슨하게 잡고, 실제 브라우저에서 돌리는 E2E 테스트에 사파리를 포함해야 이 차이가 잡힙니다.

함께 확인하면 좋은 것

붙여 넣은 JSON의 오류를 한국어로 설명하고 위치를 짚어 주는 JSON 정렬·검사기를 쓰세요. JSON이 아닌 형식(CSV·YAML·TOML)이라면 JSON 변환기로 먼저 바꾸고, 문자열 안에 이스케이프된 JSON은 Base64 변환기가 아니라 정렬기의 문자열 풀기로 벗기면 됩니다.

이 글에서 다룬 도구

더 읽을 글

  • 2026년 4대보험 요율 총정리

    국민연금 9.5%, 건강보험 7.19%, 장기요양 13.14%, 고용보험 1.8%. 근로자·사업주 부담과 월 보수별 보험료, 국민연금 인상 로드맵을 정리했습니다.

  • 2026년 연봉 실수령액표

    연봉 2,000만 원부터 1억 원까지 500만 원 단위로 월 실수령액과 공제율을 정리했습니다. 부양가족·비과세·퇴직금 포함 여부에 따른 차이도 함께 봅니다.

  • 2026년 최저임금 월급·주휴수당 계산법

    시급 10,320원, 월 2,156,880원. 209시간의 정체, 주 근로시간별 알바 주급·월급, 야간·연장 가산, 수습 감액 조건과 위반 대응을 정리했습니다.

  • 퇴직금 지급 조건과 평균임금 계산 방법

    1년 이상·주 15시간 이상이면 아르바이트도 받습니다. 평균임금에 들어가는 것과 빠지는 것, 상여금 3/12, 통상임금 보장, DB·DC형 차이, 지급 기한을 정리했습니다.