Microsoft MVP성태의 닷넷 이야기
오류 유형: 468. JSON.parse가 허용하지 않는 문자 [링크 복사], [링크+제목 복사]
조회: 17917
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 
(연관된 글이 1개 있습니다.)

JSON.parse가 허용하지 않는 문자

아래의 웹 페이지를 브라우저로 보면,

<!DOCTYPE html>

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title></title>

    <script>
        function parse_input() {
            var txt = '{"firstname":"My\tName"}';
            var obj = JSON.parse(txt);
        }
    </script>

</head>
<body>
    <input type="button" onclick="parse_input()" value="test" />
</body>
</html>

F12 개발자 콘솔을 통해 parse_input 함수 수행 시 다음과 같은 오류가 나는 것을 볼 수 있습니다.

[Internet Explorer 11]

SCRIPT1014: Invalid character
encode_sample.html (15,13)

[Edge]

SCRIPT5655: SCRIPT5655: JSON.parse Error: Invalid character at position:17

[Chrome]

VM32:1 Uncaught SyntaxError: Unexpected token    in JSON at position 18
    at JSON.parse (<anonymous>)
    at parse_input (encode_sample.html:15)
    at HTMLInputElement.onclick (encode_sample.html:22)

원인은 json 텍스트에 포함된 탭(\t) 문자 때문입니다. 따라서 이런 문제를 예방하려면 해당 텍스트들의 입력 시에 미리 json parse로 테스트하든가, 아니면 다음과 같은 식으로 문자열 필터링을 하는 것이 좋습니다.

function check_validInput(txt) {
    var replacedText = txt.replace(/[\u0000-\u0019]+/g, " "); // https://stackoverflow.com/questions/14432165/uncaught-syntaxerror-unexpected-token-with-json-parse/41498991
    if (txt != replacedText) {
        alert("INVALID");
        return false;
    }

    alert("VALID");
    return true;
}




재미있는 것은, 이러한 탭 문자 등의 것들이 본래 JSON 표준의 제약은 아니라는 점입니다. 일례로, C#의 JSON.NET으로 실행하면 잘 파싱이 됩니다.

using System;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            string txt = "{ \"firstname\": \"test\t\tqwer\" }";
            object result = Newtonsoft.Json.JsonConvert.DeserializeObject(txt);
            Console.WriteLine(result);
        }
    }
}

/*
출력 결과:

{
  "firstname": "test\t\tqwer"
}
*/

그런데 3개의 브라우저(IE, Edge, Chrome) 모두 마치 서로 같은 JSON parser 코드를 공유하기라도 한 것처럼 오류가 발생한다는 것이 매우 흥미롭습니다. 사실, JSON 포맷 표준에서는,

JSON 데이터 교환 포맷
; https://techhtml.github.io/ECMA-404/

ECMA-404
; http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf

문자열의 범위로,

String: a sequence of zero or more Unicode characters. Strings are delimited with double-quotation marks and support a backslash escaping syntax.

분명히 '\'를 이용한 이스케이프 문자를 지원한다고 명시합니다. 또한, 다음의 표준 문서를 봐도,

Standard ECMA-262
5.1 Edition / June 2011
ECMAScript® Language Specification
; https://www.ecma-international.org/ecma-262/5.1/#sec-15.12.2

JSON 구문의 가능한 값으로 "JSONString"이라고 명시하고 있으며 또한 stringify의 "NOTE 3"에 다음과 같은 문구가 나옵니다.

String values are wrapped in double quotes. The characters " and \ are escaped with \ prefixes. Control characters are replaced with escape sequences \uHHHH, or with the shorter forms, \b (backspace), \f (formfeed), \n (newline), \r (carriage return), \t (tab).

혹시 왜? 브라우저의 JSON.parse 메서드가 \b, \f, \n, \r, \t 문자들에 대해 파싱 오류가 발생하는지 정확한 이력을 아시는 분은 덧글 부탁드립니다. ^^




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 6/5/2018]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2018-06-05 12시49분
[spowner] \\t 이런 식으로 표기해야 하는게 아닐까요? json문서는 \ 문자들을 처리한다고 이해됩니다...
[guest]
2018-06-05 01시52분
아래의 문장을 두고 의견을 주신 것 같은데,

"The characters " and \ are escaped with \ prefixes."

위의 것은 겹따옴표와 역슬래시 문자를 표현하고 싶으면 그에 대해 역슬래시 문자를 하나 더 추가해야 한다는 것입니다. 그 외의 것들은 모두 역슬래시가 하나로 되어 있는 것이 맞습니다.

\\t 이런 식으로 표기하면 JSON에게는 앞의 역슬래시 2개가 '\' 문자로 해석되어 결국 '\', 't' 2글자로 인식됩니다. 즉 탭 문자로 인식하지 않습니다.
정성태
2018-06-05 02시13분
[spowner] var txt = '{"firstname":"My\\tName"}';
에서 이미 \t는 탭으로 해석이 되고
탭은 JSON.parse에서 오류를 발생합니다.
\" 을 포함해서 JSON.parse에 역슬래쉬를 넣어줘야 합니다.
즉, 탭은
var txt = '{"firstname":"My\\tName"}';
으로 JSON.parse에 "\t"으로('\', 't' 두글자로) 넘겨줘야 합니다.
문서로 봤을 때 저는 그렇게 이해했습니다
[guest]
2018-06-05 02시16분
[spowner] 위의 댓글 첫번째 코드가 역슬래쉬 두개로 들어갔는지 모르겠네요. 하나입니다; JSON String 정의에는 U+0000부터 U+001F의 컨트롤 문자는 직접 표현할 수 없습니다. 탭의 경우 이 범위안에 들어가 직접 표현이 안되고 앞에 역슬래쉬를 붙어야 합니다
[guest]
2018-06-05 02시57분
방금 전 덧글은 테스트 안 하고 쓴 글인데, ^^; 테스트를 해보니 말씀해 주신 내용이 맞습니다. 게다가 JSON.NET으로 테스트를 해보니, '\t'뿐만 아니라 '\\t'도 정상적으로 탭 문자로 변환되는 것을 확인했습니다. 그렇게 보면 오히려 JSON.NET이 '\t'에 대한 처리를 부가적으로 (편의상) 해주는 것 같습니다.

결국, JSON 텍스트를 주고 받을 때 모든 문자열에 대해 JSON parser에 전달하려면 탭 문자를 (포함해 몇몇 이스케이프 문자를) "\t"에서 "\\t"와 같은 식으로 바꿔야 한다는 불편함이 발생합니다.

실제로 Javascript의 문자열은 '\t'를 탭 문자로 인식합니다.

  var txt = '{"firstname":"My\tName"}';
  alert("txt: " + txt); // My Name

물론 HTTPRequest 등을 통해 서버로부터 전송받은 문자열의 탭 문자도 저런 식으로 옵니다.

따라서 \t (뿐만 아니라 \n 등의 몇몇 이스케이프 문자)를 포함한 문자열을 \\t와 같은 식으로 반드시 전처리를 해줘야 하고,

  var txt = '{"firstname":"My\tName"}';
  var replacedText = txt.replace(/\t/g, "\\t")
                        .replace(/\n/g, "\\n");
                
  var obj = JSON.parse(replacedText);

이어서 JSON.stringify로 반환한 문자열도 마찬가지로 \\t를 다시 \t로 바꿔주는 후처리를 해줘야 하는 식이 됩니다. 그럼 이제 JSON이 왜 이런 식으로 불편한 처리를 유도했는지에 대해 궁금해지는군요. ^^
정성태
2018-06-05 03시08분
생각해 보니, 완전히 문자열로 표현 가능하게 만든다는 취지에서 봤을 때 \\t로 하는 것이 일관성이 있는 것 같습니다. 전/후처리가 필요하다는 단점은 있지만.
정성태
2018-06-05 03시22분
[spowner] 정성태님의 댓글에 동의합니다. 정의라는게 관점에 따라 다르긴 해서... 스펙이란게 그런것을 강제하는 효과도 있는 것 같아요
[guest]
2018-06-05 03시39분
그나저나, spowner님 같은 분들의 덧글 덕분에 블로그하는 맛이 납니다. ^^ 이렇게 글을 안 썼다면 모르고 지나갔을 테니.
정성태
2018-06-05 12시12분
댓글의 내용을 반영해 다음의 글로 재작성했습니다.

JSON의 escape sequence 문자 처리 방식
; http://www.sysnet.pe.kr/2/0/11532
정성태

... 31  32  33  [34]  35  36  37  38  39  40  41  42  43  44  45  ...
NoWriterDateCnt.TitleFile(s)
12772정성태8/11/20219348스크립트: 21. 파이썬 - 윈도우 환경에서 개발한 Django 앱을 WSL 환경의 uwsgi를 이용해 실행 [1]
12771정성태8/11/20217728Windows: 196. "Microsoft Windows Subsystem for Linux Background Host" / "Vmmem"을 종료하는 방법
12770정성태8/11/20218429.NET Framework: 1086. C# - Windows Forms 응용 프로그램의 자식 컨트롤 부하파일 다운로드1
12769정성태8/11/20216414오류 유형: 752. Python - ImportError: No module named pip._internal.cli.main 두 번째 이야기
12768정성태8/10/20217439.NET Framework: 1085. .NET 6에 포함된 신규 BCL API [1]파일 다운로드1
12767정성태8/10/20218498오류 유형: 752. Python - ImportError: No module named pip._internal.cli.main
12766정성태8/9/20217023Java: 32. closing inbound before receiving peer's close_notify
12765정성태8/9/20216341Java: 31. Cannot load JDBC driver class 'org.mysql.jdbc.Driver'
12764정성태8/9/202144804Java: 30. XML document from ServletContext resource [/WEB-INF/applicationContext.xml] is invalid
12763정성태8/9/20217822Java: 29. java.lang.NullPointerException - com.mysql.jdbc.ConnectionImpl.getServerCharset
12762정성태8/8/202111341Java: 28. IntelliJ - Unable to open debugger port 오류
12761정성태8/8/20218556Java: 27. IntelliJ - java: package javax.inject does not exist [2]
12760정성태8/8/20215970개발 환경 구성: 594. 전용 "Command Prompt for ..." 단축 아이콘 만들기
12759정성태8/8/20219091Java: 26. IntelliJ + Spring Framework + 새로운 Controller 추가 [2]파일 다운로드1
12758정성태8/7/20218432오류 유형: 751. Error assembling WAR: webxml attribute is required (or pre-existing WEB-INF/web.xml if executing in update mode)
12757정성태8/7/20219116Java: 25. IntelliJ + Spring Framework 프로젝트 생성
12756정성태8/6/20217934.NET Framework: 1084. C# - .NET Core Web API 단위 테스트 방법 [1]파일 다운로드1
12755정성태8/5/20217067개발 환경 구성: 593. MSTest - 단위 테스트에 static/instance 유형의 private 멤버 접근 방법파일 다운로드1
12754정성태8/5/20218001오류 유형: 750. manage.py - Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
12753정성태8/5/20218221오류 유형: 749. PyCharm - Error: Django is not importable in this environment
12752정성태8/4/20216318개발 환경 구성: 592. JetBrains의 IDE(예를 들어, PyCharm)에서 Visual Studio 키보드 매핑 적용
12751정성태8/4/20219360개발 환경 구성: 591. Windows 10 WSL2 환경에서 docker-compose 빌드하는 방법
12750정성태8/3/20216179디버깅 기술: 181. windbg - 콜 스택의 "Call Site" 오프셋 값이 가리키는 위치
12749정성태8/2/20215596개발 환경 구성: 590. Visual Studio 2017부터 단위 테스트에 DataRow 특성 지원
12748정성태8/2/20216208개발 환경 구성: 589. Azure Active Directory - tenant의 관리자(admin) 계정 로그인 방법
12747정성태8/1/20216799오류 유형: 748. 오류 기록 - MICROSOFT GRAPH – HOW TO IMPLEMENT IAUTHENTICATIONPROVIDER파일 다운로드1
... 31  32  33  [34]  35  36  37  38  39  40  41  42  43  44  45  ...