Microsoft MVP성태의 닷넷 이야기
오류 유형: 468. JSON.parse가 허용하지 않는 문자 [링크 복사], [링크+제목 복사],
조회: 21764
글쓴 사람
정성태 (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
정성태

... 76  77  78  79  [80]  81  82  83  84  85  86  87  88  89  90  ...
NoWriterDateCnt.TitleFile(s)
11810정성태2/11/201914620.NET Framework: 808. .NET Profiler - GAC 모듈에서 GAC 비-등록 모듈을 참조하는 경우의 문제
11809정성태2/11/201916031.NET Framework: 807. ClrMD를 이용해 메모리 덤프 파일로부터 특정 인스턴스를 참조하고 있는 소유자 확인
11808정성태2/8/201917170디버깅 기술: 123. windbg - 닷넷 응용 프로그램의 메모리 누수 분석
11807정성태1/29/201915115Windows: 156. 가상 디스크의 용량을 복구 파티션으로 인해 늘리지 못하는 경우 [4]
11806정성태1/29/201915439디버깅 기술: 122. windbg - 덤프 파일로부터 PID와 환경 변수 등의 정보를 구하는 방법
11805정성태1/28/201916893.NET Framework: 806. C# - int []와 object []의 차이로 이해하는 제네릭의 필요성 [4]파일 다운로드1
11804정성태1/24/201915105Windows: 155. diskpart - remove letter 이후 재부팅 시 다시 드라이브 문자가 할당되는 경우
11803정성태1/10/201914159디버깅 기술: 121. windbg - 닷넷 Finalizer 스레드가 멈춰있는 현상
11802정성태1/7/201915866.NET Framework: 805. 두 개의 윈도우를 각각 실행하는 방법(Windows Forms, WPF)파일 다운로드1
11801정성태1/1/201917250개발 환경 구성: 427. Netsh의 네트워크 모니터링 기능 [3]
11800정성태12/28/201816217오류 유형: 509. WCF 호출 오류 메시지 - System.ServiceModel.CommunicationException: Internal Server Error
11799정성태12/19/201817323.NET Framework: 804. WPF(또는 WinForm)에서 UWP UI 구성 요소 사용하는 방법 [3]파일 다운로드1
11798정성태12/19/201816483개발 환경 구성: 426. vcpkg - "Building vcpkg.exe failed. Please ensure you have installed Visual Studio with the Desktop C++ workload and the Windows SDK for Desktop C++"
11797정성태12/19/201813603개발 환경 구성: 425. vcpkg - CMake Error: Problem with archive_write_header(): Can't create '' 빌드 오류
11796정성태12/19/201813192개발 환경 구성: 424. vcpkg - "File does not have expected hash" 오류를 무시하는 방법
11795정성태12/19/201816015Windows: 154. PowerShell - Zone 별로 DNS 레코드 유형 정보 조회 [1]
11794정성태12/16/201812543오류 유형: 508. Get-AzureWebsite : Request to a downlevel service failed.
11793정성태12/16/201814675개발 환경 구성: 423. NuGet 패키지 제작 - Native와 Managed DLL을 분리하는 방법 [1]
11792정성태12/11/201815455Graphics: 34. .NET으로 구현하는 OpenGL (11) - Per-Pixel Lighting파일 다운로드1
11791정성태12/11/201815343VS.NET IDE: 130. C/C++ 프로젝트의 시작 프로그램으로 .NET Core EXE를 지정하는 경우 닷넷 디버깅이 안 되는 문제 [1]
11790정성태12/11/201813380오류 유형: 507. Could not save daemon configuration to C:\ProgramData\Docker\config\daemon.json: Access to the path 'C:\ProgramData\Docker\config' is denied.
11789정성태12/10/201824731Windows: 153. C# - USB 장치의 연결 및 해제 알림을 위한 WM_DEVICECHANGE 메시지 처리 [2]파일 다운로드2
11788정성태12/4/201813322오류 유형: 506. SqlClient - Value was either too large or too small for an Int32.Couldn't store <2151292191> in ... Column
11787정성태11/29/201817506Graphics: 33. .NET으로 구현하는 OpenGL (9), (10) - OBJ File Format, Loading 3D Models파일 다운로드1
11786정성태11/29/201814242오류 유형: 505. OpenGL.NET 예제 실행 시 "Managed Debugging Assistant 'CallbackOnCollectedDelegate'" 예외 발생
11785정성태11/21/201816561디버깅 기술: 120. windbg 분석 사례 - ODP.NET 사용 시 Finalizer에서 System.AccessViolationException 예외 발생으로 인한 비정상 종료
... 76  77  78  79  [80]  81  82  83  84  85  86  87  88  89  90  ...