Microsoft MVP성태의 닷넷 이야기
.NET Framework: 351. JavaScriptSerializer, DataContractJsonSerializer, Json.NET [링크 복사], [링크+제목 복사],
조회: 25772
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일

(시리즈 글이 9개 있습니다.)
.NET Framework: 351. JavaScriptSerializer, DataContractJsonSerializer, Json.NET
; https://www.sysnet.pe.kr/2/0/1391

.NET Framework: 661. Json.NET의 DeserializeObject 수행 시 속성 이름을 동적으로 바꾸는 방법
; https://www.sysnet.pe.kr/2/0/11224

.NET Framework: 756. JSON의 escape sequence 문자 처리 방식
; https://www.sysnet.pe.kr/2/0/11532

사물인터넷: 54. 아두이노 환경에서의 JSON 파서(ArduinoJson) 사용법
; https://www.sysnet.pe.kr/2/0/11766

.NET Framework: 1073. C# - JSON 역/직렬화 시 리플렉션 손실을 없애는 JsonSrcGen
; https://www.sysnet.pe.kr/2/0/12688

.NET Framework: 2087. .NET 6부터 SourceGenerator와 통합된 System.Text.Json
; https://www.sysnet.pe.kr/2/0/13214

.NET Framework: 2115. System.Text.Json의 역직렬화 시 필드/속성 주의
; https://www.sysnet.pe.kr/2/0/13342

닷넷: 2261. C# - 구글 OAuth의 JWT (JSON Web Tokens) 해석
; https://www.sysnet.pe.kr/2/0/13623

닷넷: 2265. C# - System.Text.Json의 기본적인 (한글 등에서의) escape 처리
; https://www.sysnet.pe.kr/2/0/13644




JavaScriptSerializer, DataContractJsonSerializer, Json.NET


닷넷에는 기본적으로 JavaScriptSerializer, DataContractJsonSerializer 타입이 JSON 직렬화와 관련되어 제공됩니다. 각각 장단점이 있는데요. JavaScriptSerializer는 부가적인 작업 없이 모든 객체의 값을 Key/Value의 쌍으로 직렬화/역직렬화할 수 있습니다. 즉, strong-type이 제공되지 않습니다. 이를 보완하기 위해 클래스를 이용한 strong-type을 지원하는 DataContractJsonSerializer를 사용할 수 있는데, 당연히 클래스 구조는 그에 맞게 만들어 주어야 합니다.

예를 들어, 다음은 페이스북의 me 쿼리의 일부를 표현한 것인데요.

{
"id":"ttt",
"work":[
	{"employer":{"id":"zzz","name":"..."},
	 "start_date":"1992-01",
	}
],
"timezone":9,
"verified":true,
"updated_time":"2012-11-16T07:23:07+0000"
}

DataContractJsonSerializer로 역직렬화하기 위해 다음과 같이 클래스들을 만들어 주고,

public class GraphUser
{
    public string id { get; set; }

    public GraphWork[] work { get; set; }

    public int timezone { get; set; }
    public bool verified { get; set; }
    public string updated_time { get; set; }
}

public class GraphEmployer
{
    public string id { get; set; }
    public string name { get; set; }
}

public class GraphWork
{
    public GraphEmployer employer { get; set; }
    public string start_date { get; set; }
}

이렇게 코딩을 해주면 됩니다.

static void Main(string[] args)
{
    string json = "{ \"id\":\"ttt\", \"work\":[ 	{\"employer\":{\"id\":\"zzz\",\"name\":\"...\"},  \"start_date\":\"1992-01\" } ], \"timezone\":9, \"verified\":true, \"updated_time\":\"2012-11-16T07:23:07+0000\" }";

    DataContractJsonSerializer dcjs = new DataContractJsonSerializer(typeof(GraphUser));
    GraphUser user2 = dcjs.ReadObject(StringToStream(json)) as GraphUser;

    Console.WriteLine(user2.id);
    Console.WriteLine(user2.work[0].employer.id);
}




그런데, 여기서 문제가 있습니다. 만약 string 데이터에 "\n"를 포함하게 되면 DataContractJsonSerializer.ReadObject 호출 시에 예외가 발생합니다. 예를 들어, 위의 json 문자열의 id 값이 다음과 같은 경우입니다.

    string json = "{ \"id\":\"t\ntt\", ...[생략]... }";

그럼, 이런 예외 메시지가 발생합니다.

Unhandled Exception: System.Runtime.Serialization.SerializationException: Therewas an error deserializing the object of type ConsoleApplication1.GraphUser. Encountered invalid character ''. ---> System.FormatException: Encountered invalid character ''.
   at System.Runtime.Serialization.Json.XmlJsonReader.ComputeQuotedTextLengthUntilEndQuote(Byte[] buffer, Int32 offset, Int32 offsetMax, Boolean& escaped)
   at System.Runtime.Serialization.Json.XmlJsonReader.ReadQuotedText(Boolean moveToText)
   at System.Runtime.Serialization.Json.XmlJsonReader.Read()
   at System.Xml.XmlBaseReader.ReadElementContentAsString()
   at System.Runtime.Serialization.XmlReaderDelegator.ReadElementContentAsString()
   ...[생략]...
   at System.Runtime.Serialization.Json.DataContractJsonSerializer.ReadObject(XmlDictionaryReader reader)
   at System.Runtime.Serialization.Json.DataContractJsonSerializer.ReadObject(Stream stream)
   at ConsoleApplication1.Program.Main(String[] args) in d:\...\Program.cs:line 28

문제의 원인은 "\n" 문자에서 역슬래시가 1개가 아닌 2개여야 한다는 점입니다. 그래서, 다음과 같이 변경해 주면 정상적으로 역직렬화를 합니다.

    string json = "{ \"id\":\"t\\ntt\", ...[생략]... }";

// 또는

    string json = "{ \"id\":\"t\\u000att\", ...[생략]... }";

그런데, 이런 경우가 왜 발생할까요? 물론 DataContractJsonSerializer로 직렬화했다면 저런 식으로 문자열이 나오지 않습니다. 제가 경험했던 곳은 바로 페이스북의 /me 쿼리에서 저런 결과를 볼 수 있었습니다. 따라서, 페이스북으로부터 json 문자열을 받는다면 반드시 '\n' 문자를 '\\n'으로 치환해 주는 작업이 선행되어야 합니다.




\n 문자에 대한 처리를 하지 않고 역직렬화를 하고 싶다면 Json.NET을 이용하면 됩니다. (다운로드 및 그에 대한 소스 코드는 모두 공개되어 있습니다.)

Json.NET
; http://json.codeplex.com/

Nuget을 지원하기 때문에 Visual Studio 2012에서 "View" / "Other Windows" / "Package Manager Console" 창을 띄우고, 다음과 같이 입력하면 Json.NET 이 프로젝트에 추가됩니다.

Package Manager Console Host Version 2.1.31002.9028

Type 'get-help NuGet' to see all available NuGet commands.

PM> Install-Package Newtonsoft.Json
Successfully installed 'Newtonsoft.Json 4.5.11'.
Successfully added 'Newtonsoft.Json 4.5.11' to ConsoleApplication1.

PM> 

다음은 Json.NET을 이용한 간단한 코드입니다.

string json = "{ \"id\":\"t\ntt\", \"work\":[ 	{\"employer\":{\"id\":\"zzz\",\"name\":\"...\"},  \"start_date\":\"1992-01\" } ], \"timezone\":9, \"verified\":true, \"updated_time\":\"2012-11-16T07:23:07+0000\" }";

GraphUser user = JsonConvert.DeserializeObject<GraphUser>(json);

Json.NET이 좋은 또 다른 이유가 하나 있습니다. 바로 DateTime 값의 역직렬화입니다. 예제의 json 문자열에 보면 updated_time의 값이 "2012-11-16T07:23:07+0000"인데 이를 그냥 "string" 타입으로 역직렬화하고 있지만, 만약 이 필드의 타입을 DateTime으로 바꾸게 되면,

public class GraphUser
{
    public string id { get; set; }

    public GraphWork[] work { get; set; }

    public int timezone { get; set; }
    public bool verified { get; set; }
    public DateTime updated_time { get; set; }
}

DataContractJsonSerializer으로 역직렬화하는 경우 다음과 같은 예외가 발생합니다.

There was an error deserializing the object of type ConsoleApplication1.GraphUser. DateTime content '2012-11-16T07:23:07+0000' does not start with '\/Date(' and end with ')\/' as required for JSON.


이런 상황에서 표준이냐 아니냐의 문제는 중요하지 않습니다. (그 유명한 페이스북에서 이런 식으로 데이터를 넘겨주기 때문에. ^^;)

하지만, 역시 이를 Json.NET으로 역직렬화하면 잘 됩니다. "2012-11-16T07:23:07+0000" UTC 시간이므로 변환하고 나면 한글 윈도우의 경우 +9 시간대를 적용받아 "{2012-11-16 16:23:07}" 값으로 계산합니다.

결론은, 3가지 Json 직렬화 방법 중에서 가능한 Json.NET을 이용하시는 것이 좋습니다. ^^
(첨부한 파일은 위의 간단한 코드를 담고 있는 테스트 프로젝트입니다.)




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







[최초 등록일: ]
[최종 수정일: 5/9/2024]

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

비밀번호

댓글 작성자
 



2012-12-21 11시20분
[lancers] 성능 문제도 있어요.
JSON.NET이 갑이었던 걸로 기억함...
[guest]
2012-12-22 12시22분
^^ 성능까지 좋았군요. 정보 감사드립니다.
정성태
2012-12-22 01시49분
[lancers] ASP.NET Web API에서 default json serializer를 아예 Json.net올 바꾼다고 했었는데, RTM에서도 유효한지 모르겠네요.
RC에서는 확실히 반영되어 있었습니다.
근데, 요즘 ASP.NET을 쓸 일이 없어서 확인을 못했네요.. ㅋㅋ
[guest]

... 121  122  123  124  125  126  127  128  129  130  [131]  132  133  134  135  ...
NoWriterDateCnt.TitleFile(s)
1778정성태10/10/201418062오류 유형: 247. The Netlogon service could not create server share C:\Windows\SYSVOL\sysvol\[도메인명]\SCRIPTS.
1777정성태10/10/201421166오류 유형: 246. The processing of Group Policy failed. Windows attempted to read the file \\[도메인]\sysvol\[도메인]\Policies\{...GUID...}\gpt.ini
1776정성태10/10/201418176오류 유형: 245. 이벤트 로그 - Name resolution for the name _ldap._tcp.dc._msdcs.[도메인명]. timed out after none of the configured DNS servers responded.
1775정성태10/9/201419274오류 유형: 244. Visual Studio 디버깅 (2) - Unable to break execution. This process is not currently executing the type of code that you selected to debug.
1774정성태10/9/201426524개발 환경 구성: 246. IIS 작업자 프로세스의 20분 자동 재생(Recycle)을 끄는 방법
1773정성태10/8/201429717.NET Framework: 471. 웹 브라우저로 다운로드가 되는 파일을 왜 C# 코드로 하면 안되는 걸까요? [1]
1772정성태10/3/201418476.NET Framework: 470. C# 3.0의 기본 인자(default parameter)가 .NET 1.1/2.0에서도 실행될까? [3]
1771정성태10/2/201427995개발 환경 구성: 245. 실행된 프로세스(EXE)의 명령행 인자를 확인하고 싶다면 - Sysmon [4]
1770정성태10/2/201421594개발 환경 구성: 244. 매크로 정의를 이용해 파일 하나로 C++과 C#에서 공유하는 방법 [1]파일 다운로드1
1769정성태10/1/201424012개발 환경 구성: 243. Scala 개발 환경 구성(JVM, 닷넷) [1]
1768정성태10/1/201419416개발 환경 구성: 242. 배치 파일에서 Thread.Sleep 효과를 주는 방법 [5]
1767정성태10/1/201424580VS.NET IDE: 94. Visual Studio 2012/2013에서의 매크로 구현 - Visual Commander [2]
1766정성태10/1/201422383개발 환경 구성: 241. 책 "프로그래밍 클로저: Lisp"을 읽고 나서. [1]
1765정성태9/30/201425955.NET Framework: 469. Unity3d에서 transform을 변수에 할당해 사용하는 특별한 이유가 있을까요?
1764정성태9/30/201422174오류 유형: 243. 파일 삭제가 안 되는 경우 - The action can't be comleted because the file is open in System
1763정성태9/30/201423781.NET Framework: 468. PDB 파일을 연동해 소스 코드 라인 정보를 알아내는 방법파일 다운로드1
1762정성태9/30/201424486.NET Framework: 467. 닷넷에서 EIP/RIP 레지스터 값을 구하는 방법 [1]파일 다운로드1
1761정성태9/29/201421487.NET Framework: 466. 윈도우 운영체제의 보안 그룹 이름 및 설명 문자열을 바꾸는 방법파일 다운로드1
1760정성태9/28/201419756.NET Framework: 465. ICorProfilerInfo::GetILToNativeMapping 메서드가 0x80131358을 반환하는 경우
1759정성태9/27/201430954개발 환경 구성: 240. Visual C++ / x64 환경에서 inline-assembly를 매크로 어셈블리로 대체하는 방법파일 다운로드1
1758정성태9/23/201437771개발 환경 구성: 239. 원격 데스크톱 접속(RDP)을 기존의 콘솔 모드처럼 사용하는 방법 [1]
1757정성태9/23/201418357오류 유형: 242. Lync로 모임 참여 시 소리만 들리지 않는 경우 - 두 번째 이야기
1756정성태9/23/201427332기타: 48. NVidia 제품의 과다한 디스크 사용 [2]
1755정성태9/22/201434110오류 유형: 241. Unity Web Player를 설치해도 여전히 설치하라는 화면이 나오는 경우 [4]
1754정성태9/22/201424486VC++: 80. 내 컴퓨터에서 C++ AMP 코드가 실행이 될까요? [1]
1753정성태9/22/201420470오류 유형: 240. Lync로 세미나 참여 시 소리만 들리지 않는 경우 [1]
... 121  122  123  124  125  126  127  128  129  130  [131]  132  133  134  135  ...