logo MSJO.kr

C#, JSON in Native AOT

2024-11-19
MsJ
 

C#(.NET)으로 만든 앱을 네이티브 AOT로 게시하면 자체 포함 배포처럼 네이티브 코드로 AOT(ahead-of-time) 컴파일된 앱이 생성된다. 즉 IL(Intermediate language)을 네이티브 코드로 컴파일한다.1

System.Text.Json에서 원본을 생성하는 방법은 몇 가지 추가 및 제약 사항이 있는데 아래의 예제는 이를 설명하는 소스이다.2 테스트를 위해 생성한 파일은 Program.cs, TestModel.cs, JsonHelper.cs이다.

테스트를 위하여 콘솔 프로젝트를 만들고 프로젝트 설정(~.csproj)을 아래와 같이 변경하고 터미널에서 dotnet publish -c Release -r win-x64 빌드한다. 정상적으로 빌드가 완료되면 하위 폴더 Native에 실행파일이 생성된다.

프로젝트 설정(변경)
<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net8.0</TargetFramework>
        <Nullable>enable</Nullable>
        <PublishAot>true</PublishAot>
        <IsAotCompatible>true</IsAotCompatible>
    </PropertyGroup>
</Project>
TestModel.cs
using System.Text.Json.Serialization;

namespace ConsoleTest;

public class TestModel : IC
{
    public string FirstName { get; set; } = string.Empty;
    public string LastName { get; set; } = string.Empty;
    public string FullName { get; set; } = string.Empty;

    // [JsonConverter(typeof(JsonStringEnumConverter<ETest>))]
    public ETest ETest { get; set; } = 0;
    public string CodeName { get; set; } = "CodeName";
}

public interface IA
{
    [JsonPropertyOrder(1)]
    string FirstName { get; set; }
}

public interface IB : IA
{
    [JsonPropertyOrder(2)]
    string LastName { get; set; }
}

public interface IC : IB
{
    [JsonPropertyOrder(3)]
    string FullName { get; set; }

    [JsonPropertyOrder(4)]
    ETest ETest { get; set; }

    [JsonIgnore]
    string CodeName { get; set; }
}

[JsonConverter(typeof(JsonStringEnumConverter<ETest>))]
public enum ETest
{
    Cpp,
    Python,
    Go
}

// [JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Serialization)]
// [JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)]
[JsonSerializable(typeof(IA))]
[JsonSerializable(typeof(IB))]
[JsonSerializable(typeof(IC))]
[JsonSerializable(typeof(TestModel))]
//[JsonSerializable(typeof(bool))] //class 프로퍼티가 object type일 경우 데이터 타입 명시
//[JsonSerializable(typeof(int))]
public partial class TestModelContext : JsonSerializerContext;

public static class AotMessage
{
    public const string AOT = "Need runtime code generation for native AOT(may break when trimming)";
}
JsonHelper.cs
using System;
using System.Diagnostics.CodeAnalysis;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;

namespace ConsoleTest;

public static class JsonHelper
{
    private static JsonSerializerOptions Options;

    static JsonHelper()
    {
        Options = new JsonSerializerOptions();
    }

    [RequiresUnreferencedCode(AotMessage.AOT)]
    [RequiresDynamicCode(AotMessage.AOT)]
    public static string ModelToJson<T>(T modelClass, IJsonTypeInfoResolver resolver)
    {
        try
        {
            if (typeof(T).Name == "String")
            {
                return "string.Empty1";
            }

            if (!(typeof(T).IsInterface || typeof(T).IsClass))
            {
                return "string.Empty2";
            }

            Options = new JsonSerializerOptions
            {
                Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
                PropertyNameCaseInsensitive = true,
                PropertyNamingPolicy = null,
                TypeInfoResolver = resolver
            };

            string _result = JsonSerializer.Serialize(modelClass, Options);
            return string.IsNullOrWhiteSpace(_result) ? string.Empty : _result;
        }
        catch (Exception _ex)
        {
            return _ex.Message;
        }
    }

    [RequiresUnreferencedCode(AotMessage.AOT)]
    [RequiresDynamicCode(AotMessage.AOT)]
    public static T JsonToModel<T>(string jsonString, IJsonTypeInfoResolver resolver) where T : class, new()
    {
        try
        {
            Options = new JsonSerializerOptions
            {
                Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
                PropertyNameCaseInsensitive = true,
                PropertyNamingPolicy = null,
                TypeInfoResolver = resolver
            };

            T? _result = JsonSerializer.Deserialize<T>(jsonString, Options);
            return _result ?? new T();
        }
        catch
        {
            return new T();
        }
    }
}
Program.cs
using System;
using System.Diagnostics.CodeAnalysis;

namespace ConsoleTest;

//Use concrete types when possible for improved performance
#pragma warning disable CA1859
internal class Program
{
    [RequiresUnreferencedCode(AotMessage.AOT)]
    [RequiresDynamicCode(AotMessage.AOT)]
    private static void Main()
    {
        IA _ia = new TestModel();
        IB _ib = new TestModel();
        IC _ic = new TestModel();

        _ia.FirstName = "a1가";

        _ib.FirstName = "b1";
        _ib.LastName = "b2";

        _ic.FirstName = "c1";
        _ic.LastName = "c2";
        _ic.FullName = "c3";
        _ic.ETest = ETest.Python;

        TestModelContext _context = new();

        string _result = JsonHelper.ModelToJson(_ia, _context);
        Console.WriteLine($"{_result}");

        IA _a = JsonHelper.JsonToModel<TestModel>(_result, _context);
        Console.WriteLine($"{_a.FirstName}");

        Console.WriteLine("--------------------------------------------------");

        _result = JsonHelper.ModelToJson(_ib, _context);
        Console.WriteLine($"{_result}");

        IB _b = JsonHelper.JsonToModel<TestModel>(_result, _context);
        Console.WriteLine($"{_b.FirstName} {_b.LastName}");

        Console.WriteLine("--------------------------------------------------");

        _result = JsonHelper.ModelToJson(_ic, _context);
        Console.WriteLine($"{_result}");

        IC _c = JsonHelper.JsonToModel<TestModel>(_result, _context);
        Console.WriteLine($"{_c.FirstName} {_c.LastName} {_c.FullName} {_c.ETest} {_c.CodeName}");
    }
#pragma warning restore CA1859
    /*
        {"FirstName":"a1가"}
        a1가
        --------------------------------------------------
        {"FirstName":"b1","LastName":"b2"}
        b1 b2
        --------------------------------------------------
        {"FirstName":"c1","LastName":"c2","FullName":"c3","ETest":"Python"}
        c1 c2 c3 Python CodeName
    */
}
Reference
  1. Microsoft, “Native AOT deployment”
  2. Microsoft, “How to use source generation in System.Text.Json”

Prεv(Θld)   Nεxt(Nεw)
Content
Search     RSS Feed     BY-NC-ND