Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (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




C# - JSON 역/직렬화 시 리플렉션 손실을 없애는 JsonSrcGen

Source Generator를 이용해,

C# - Source Generator 소개
; https://www.sysnet.pe.kr/2/0/12223

Reflection 단계를 없애고 JSON을 직렬화/역직렬화하는 nuget 패키지가 나왔습니다. ^^

Is the era of reflection-heavy C# libraries at an end?
; https://blog.marcgravell.com/2021/05/is-era-of-reflection-heavy-c-libraries.html

JsonSrcGen
; https://github.com/trampster/JsonSrcGen

따라서 단순히 JsonSrcGen 패키지만 참조 추가하고, 다음과 같이 적절하게 특성을 적용해 주면 JSON 직렬화/역직렬화를 고속으로 수행할 수 있습니다.

using System;
using JsonSrcGen;

namespace ConsoleApp2
{
    // Install-Package JsonSrcGen
    class Program
    {
        static void Main(string[] args)
        {
            var converter = new JsonConverter();

            ReadOnlySpan<char> json = converter.ToJson(new MyType() { MyProperty = "Some value" });
            Console.WriteLine(json.ToString());

            var myType = new MyType();
            converter.FromJson(myType, "{ \"my_name\" : \"Some value\" }");
            // 또는, converter.FromJson(myType, json);

            Console.WriteLine(myType);
        }
    }


    [Json]
    public class MyType
    {
        [JsonName("my_name")]
        public string MyProperty { get; set; }

        [JsonIgnore]
        public string IgnoredProperty { get; set; }

        public override string ToString()
        {
            return $"MyProperty = {MyProperty}";
        }
    }
}

/* 출력 결과
{"my_name":"Some value"}
MyProperty = Some value
*/

이때 Source Generator가 자동 생성하는 코드는 JsonConverter이고, 위와 같은 경우 MyType에 대해 다음과 같은 식의 소스 코드를 생성해 냅니다.

// %LOCALAPPDATA%\Temp\VSGeneratedDocuments\6aae0b5e-b25f-5c50-066b-6883b1eb7a7f\JsonConverter.cs

using System;
using System.Text;
using System.Collections.Generic;

namespace JsonSrcGen
{
#nullable enable
    public class JsonConverter
    {
        [ThreadStatic]
        JsonStringBuilder? Builder;
        public ReadOnlySpan<char> ToJson(ConsoleApp2.MyType value)
        {

            var builder = Builder;
            if(builder == null)
            {
                builder = new JsonStringBuilder();
                Builder = builder;
            }
            builder.Clear();
            ToJson(value, builder);
            return builder.AsSpan();
        }
        public void ToJson(ConsoleApp2.MyType value, JsonStringBuilder builder)
        {
            if(value.MyProperty == null)
            {
                builder.Append("{\"my_name\":null");
            }
            else
            {
                builder.Append("{\"my_name\":\"");
                builder.AppendEscaped(value.MyProperty);
                builder.Append("\"");
            }
            builder.Append("}");
        }
        public ReadOnlySpan<char> FromJson(ConsoleApp2.MyType value, ReadOnlySpan<char> json)
        {
            json = json.SkipWhitespaceTo('{');
            while(true)
            {
                json = json.SkipWhitespace();
                char value452 = json[0];
                if(value452 == '\"')
                {
                    json = json.Slice(1);
                }
                else if(value452 == '}')
                {
                    return json.Slice(1);
                }
                else
                {
                    throw new InvalidJsonException($"Unexpected character! expected '}}' or '\"' but got '{value452}'", json);
                }
                var propertyName = json.ReadTo('\"');
                json = json.Slice(propertyName.Length + 1);
                json = json.SkipWhitespaceTo(':');
                switch(propertyName.Length % 1)
                {
                    case 0:
                        if(!propertyName.EqualsString("my_name"))
                        {
                            json = json.SkipProperty();
                            break;
                        }
                        json = json.Read(out string? property453Value);
                        value.MyProperty = property453Value;
                        break;
                }
                json = json.SkipWhitespace();
                if(json[0] == ',')
                {
                    json = json.Slice(1);
                }
            }
        }
    }
}
#nullable restore

재미있군요. ^^ 현재 .NET Core 2.1 이상의 프로젝트에서 사용할 수 있습니다.

(첨부 파일은 이 글의 예제 코드를 포함합니다.)




(아래의 버그는 1.1 버전에서 해결될 거라고 합니다. ^^ 대응이 무척 빠르군요.)

참고로, JsonSrcGen 대상이 되는 타입을 namespace 없이 정의하면,

using System;
using JsonSrcGen;

/*
namespace ConsoleApp2
{
*/
    [Json]
    public class MyType
    {
        [JsonName("my_name")]
        public string MyProperty { get; set; }

        [JsonIgnore]
        public string IgnoredProperty { get; set; }
    }
// }

이런 식의 무수한 컴파일 오류 메시지를 보게 될 것입니다. ^^;

Error CS0501 '<invalid-global-code>.<invalid-global-code>(value, builder)' must declare a body because it is not marked abstract, extern, or partial

Error CS0501 'JsonConverter.ToJson(global)' must declare a body because it is not marked abstract, extern, or partial

Error CS0116 A namespace cannot directly contain members such as fields or methods

Error CS1520 Method must have a return type

아직 1.0.3의 초기 버전이라 현업에 적용하실 때는 적절한 사전 검증 작업이 필요할 듯합니다.




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







[최초 등록일: ]
[최종 수정일: 6/25/2021]

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

비밀번호

댓글 작성자
 



2021-07-30 09시43분
정성태
2023-01-20 08시30분
riok/mapperly
; https://github.com/riok/mapperly

A .NET source generator for generating object mappings. No runtime reflection. Inspired by MapStruct.
정성태

... 61  62  63  64  65  66  67  68  69  70  71  72  73  [74]  75  ...
NoWriterDateCnt.TitleFile(s)
12081정성태12/16/201922358개발 환경 구성: 465. 로컬 PC에서 개발 중인 ASP.NET Core 웹 응용 프로그램을 다른 PC에서도 접근하는 방법 [5]
12080정성태12/16/201919491.NET Framework: 870. C# - 프로세스의 모든 핸들을 열람
12079정성태12/13/201921361오류 유형: 584. 원격 데스크톱(rdp) 환경에서 다중 또는 고용량 파일 복사 시 "Unspecified error" 오류 발생
12078정성태12/13/201921152Linux: 26. .NET Core 응용 프로그램을 위한 메모리 덤프 방법 [3]
12077정성태12/13/201920288Linux: 25. 자주 실행할 명령어 또는 초기 환경을 "~/.bashrc" 파일에 등록
12076정성태12/12/201918754디버깅 기술: 142. Linux - lldb 환경에서 sos 확장 명령어를 이용한 닷넷 프로세스 디버깅 - 배포 방법에 따른 차이
12075정성태12/11/201919559디버깅 기술: 141. Linux - lldb 환경에서 sos 확장 명령어를 이용한 닷넷 프로세스 디버깅
12074정성태12/10/201919204디버깅 기술: 140. windbg/Visual Studio - 값이 변경된 경우를 위한 정지점(BP) 설정(Data Breakpoint)
12073정성태12/10/201920815Linux: 24. Linux/C# - 실행 파일이 아닌 스크립트 형식의 명령어를 Process.Start로 실행하는 방법
12072정성태12/9/201917621오류 유형: 583. iisreset 수행 시 "No such interface supported" 오류
12071정성태12/9/201921135오류 유형: 582. 리눅스 디스크 공간 부족 및 safemode 부팅 방법
12070정성태12/9/201923037오류 유형: 581. resize2fs: Bad magic number in super-block while trying to open /dev/.../root
12069정성태12/2/201919432디버깅 기술: 139. windbg - x64 덤프 분석 시 메서드의 인자 또는 로컬 변수의 값을 확인하는 방법
12068정성태11/28/201928071디버깅 기술: 138. windbg와 Win32 API로 알아보는 Windows Heap 정보 분석 [3]파일 다운로드2
12067정성태11/27/201919491디버깅 기술: 137. 실제 사례를 통해 Debug Diagnostics 도구가 생성한 닷넷 웹 응용 프로그램의 성능 장애 보고서 설명 [1]파일 다운로드1
12066정성태11/27/201919109디버깅 기술: 136. windbg - C# PInvoke 호출 시 마샬링을 담당하는 함수 분석 - OracleCommand.ExecuteReader에서 OpsSql.Prepare2 PInvoke 호출 분석
12065정성태11/25/201917474디버깅 기술: 135. windbg - C# PInvoke 호출 시 마샬링을 담당하는 함수 분석파일 다운로드1
12064정성태11/25/201920345오류 유형: 580. HTTP Error 500.0/500.33 - ANCM In-Process Handler Load Failure
12063정성태11/21/201919286디버깅 기술: 134. windbg - RtlReportCriticalFailure로부터 parameters 정보 찾는 방법
12062정성태11/21/201918811디버깅 기술: 133. windbg - CoTaskMemFree/FreeCoTaskMem에서 발생한 덤프 분석 사례 - 두 번째 이야기
12061정성태11/20/201919238Windows: 167. CoTaskMemAlloc/CoTaskMemFree과 윈도우 Heap의 관계
12060정성태11/20/201920833디버깅 기술: 132. windbg/Visual Studio - HeapFree x64의 동작 분석
12059정성태11/20/201919984디버깅 기술: 131. windbg/Visual Studio - HeapFree x86의 동작 분석
12058정성태11/19/201920633디버깅 기술: 130. windbg - CoTaskMemFree/FreeCoTaskMem에서 발생한 덤프 분석 사례
12057정성태11/18/201916562오류 유형: 579. Visual Studio - Memory 창에서 유효한 주소 영역임에도 "Unable to evaluate the expression." 오류 출력
12056정성태11/18/201922198개발 환경 구성: 464. "Microsoft Visual Studio Installer Projects" 프로젝트로 EXE 서명 및 MSI 파일 서명 방법파일 다운로드1
... 61  62  63  64  65  66  67  68  69  70  71  72  73  [74]  75  ...