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의 초기 버전이라 현업에 적용하실 때는 적절한 사전 검증 작업이 필요할 듯합니다.
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]