Microsoft MVP성태의 닷넷 이야기
오류 유형: 468. JSON.parse가 허용하지 않는 문자 [링크 복사], [링크+제목 복사]
조회: 17914
글쓴 사람
정성태 (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)
12597정성태4/13/20219512개발 환경 구성: 569. csproj의 내용을 공통 설정할 수 있는 Directory.Build.targets / Directory.Build.props 파일
12596정성태4/12/20219299개발 환경 구성: 568. Windows의 80 포트 점유를 해제하는 방법
12595정성태4/12/20218707.NET Framework: 1036. SQL 서버 - varbinary 타입에 대한 문자열의 CAST, CONVERT 변환을 C# 코드로 구현
12594정성태4/11/20218178.NET Framework: 1035. C# - kubectl 명령어 또는 REST API 대신 Kubernetes 클라이언트 라이브러리를 통해 프로그래밍으로 접근 [1]파일 다운로드1
12593정성태4/10/20219322개발 환경 구성: 567. Docker Desktop for Windows - kubectl proxy 없이 k8s 대시보드 접근 방법
12592정성태4/10/20219130개발 환경 구성: 566. Docker Desktop for Windows - k8s dashboard의 Kubeconfig 로그인 및 Skip 방법
12591정성태4/9/202112354.NET Framework: 1034. C# - byte 배열을 Hex(16진수) 문자열로 고속 변환하는 방법 [2]파일 다운로드1
12590정성태4/9/20218900.NET Framework: 1033. C# - .NET 4.0 이하에서 Console.IsInputRedirected 구현 [1]
12589정성태4/8/202110209.NET Framework: 1032. C# - Environment.OSVersion의 문제점 및 윈도우 운영체제의 버전을 구하는 다양한 방법 [1]
12588정성태4/7/202110767개발 환경 구성: 565. PowerShell - New-SelfSignedCertificate를 사용해 CA 인증서 생성 및 인증서 서명 방법
12587정성태4/6/202111500개발 환경 구성: 564. Windows 10 - ClickOnce 배포처럼 사용할 수 있는 MSIX 설치 파일 [1]
12586정성태4/5/20219248오류 유형: 710. Windows - Restart-Computer / shutdown 명령어 수행 시 Access is denied(E_ACCESSDENIED)
12585정성태4/5/20218999개발 환경 구성: 563. 기본 생성된 kubeconfig 파일의 내용을 새롭게 생성한 인증서로 구성하는 방법
12584정성태4/1/20219702개발 환경 구성: 562. kubeconfig 파일 없이 kubectl 옵션만으로 실행하는 방법
12583정성태3/29/202111251개발 환경 구성: 561. kubectl 수행 시 다른 k8s 클러스터로 접속하는 방법
12582정성태3/29/20219910오류 유형: 709. Visual C++ - 컴파일 에러 error C2059: syntax error: '__stdcall'
12581정성태3/28/20219836.NET Framework: 1031. WinForm/WPF에서 Console 창을 띄워 출력하는 방법 (2) - Output 디버깅 출력을 AllocConsole로 우회 [2]
12580정성태3/28/20218604오류 유형: 708. SQL Server Management Studio - Execution Timeout Expired.
12579정성태3/28/20218625오류 유형: 707. 중첩 가상화(Nested Virtualization) - The virtual machine could not be started because this platform does not support nested virtualization.
12578정성태3/27/20218956개발 환경 구성: 560. Docker Desktop for Windows 기반의 Kubernetes 구성 (2) - WSL 2 인스턴스에 kind가 구성한 k8s 서비스 위치
12577정성태3/26/202111009개발 환경 구성: 559. Docker Desktop for Windows 기반의 Kubernetes 구성 - WSL 2 인스턴스에 kind 도구로 k8s 클러스터 구성
12576정성태3/25/20218806개발 환경 구성: 558. Docker Desktop for Windows에서 DockerDesktopVM 기반의 Kubernetes 구성 (2) - k8s 서비스 위치
12575정성태3/24/20217919개발 환경 구성: 557. Docker Desktop for Windows에서 DockerDesktopVM 기반의 Kubernetes 구성
12574정성태3/23/202111840.NET Framework: 1030. C# Socket의 Close/Shutdown 동작 (동기 모드)
12573정성태3/22/20219692개발 환경 구성: 556. WSL 인스턴스 초기 설정 명령어 [1]
12572정성태3/22/20219211.NET Framework: 1029. C# - GC 호출로 인한 메모리 압축(Compaction)을 확인하는 방법파일 다운로드1
... 31  32  33  34  35  36  37  38  39  40  [41]  42  43  44  45  ...