System.Text.Json source generator چیست؟
پایهی تمام اعمال serialization و deserialization در دات نت، استفاده از Reflection است که در زمینهی ارائهی برنامههایی با کارآیی بالا و با مصرف حافظهی پایین، بهینه عمل نمیکند. راهحل جایگزین استفاده از Reflection که در زمان اجرای برنامه رخ میدهد، به همراه دات نت 5 ارائه شد و source generators نام دارد. Source generators امکان تولید فایلهای #C را در زمان کامپایل برنامه میسر میکنند که نسبت به راهحل Reflection که در زمان اجرای برنامه فعال میشود، کارآیی بسیار بیشتری را ارائه میکنند. برای مثال به همراه دات نت 6، علاوه بر روش پیشفرض مبتنی بر Reflection ارائه شدهی توسط System.Text.Json، راه حل جدید امکان استفادهی از source generators توکار آن نیز پیش بینی شدهاست. کار اصلی آن، انجام تمام مراحلی است که پیشتر توسط Reflection در زمان اجرای برنامه صورت میگرفت، اینبار در زمان کامپایل برنامه و ارائهی آن به صورت از پیش آماده شده و مهیا.
مزایای این روش شامل موارد زیر است:
- بالا رفتن سرعت برنامه
- کاهش زمان آغاز اولیهی برنامه
- کاهش میزان حافظهی مورد نیاز برنامه
- عدم نیاز به استفادهی از System.Reflection و System.Reflection.Emit
- ارائهی Trim-compatible serialization که سبب کاهش اندازهی نهایی برنامه میشود. برای مثال در برنامههای Blazor میتوان با فعالسازی Trimming، کدهای استفاده نشده را از فایلهای بایناری نهایی حذف کرد. استفاده از source generators، با این روش سازگاری کاملی دارد.
مثالی از نحوهی کار با JSON در دات نت 6، توسط source generators آن
فرض کنید قصد داریم اعمال serialization و deserialization از نوع JSON را بر روی نمونههای کلاس زیر انجام دهیم:
namespace Test { internal class Person { public string FirstName { get; set; } public string LastName { get; set; } } }
using System.Text.Json.Serialization; namespace Test { [JsonSerializable(typeof(Person))] internal partial class MyJsonContext : JsonSerializerContext { } }
پس از آن فقط کافی است MyJsonContext را به عنوان پارامتر متدهای جدید Serialize و یا Deserialize، به صورت زیر ارسال کنیم تا از آن استفاده شود:
Person person = new() { FirstName = "Jane", LastName = "Doe" }; byte[] utf8Json = JsonSerializer.SerializeToUtf8Bytes(person, MyJsonContext.Default.Person); person = JsonSerializer.Deserialize(utf8Json, MyJsonContext.Default.Person);
متدهای جدید این API مبتنی بر source generators را در ادامه ملاحظه میکنید:
namespace System.Text.Json { public static class JsonSerializer { public static object? Deserialize(ReadOnlySpan<byte> utf8Json, Type returnType, JsonSerializerContext context) => ...; public static object? Deserialize(ReadOnlySpan<char> json, Type returnType, JsonSerializerContext context) => ...; public static object? Deserialize(string json, Type returnType, JsonSerializerContext context) => ...; public static object? Deserialize(ref Utf8JsonReader reader, Type returnType, JsonSerializerContext context) => ...; public static ValueTask<object?> DeserializeAsync(Stream utf8Json, Type returnType, JsonSerializerContext context, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static ValueTask<TValue?> DeserializeAsync<TValue>(Stream utf8Json, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static TValue? Deserialize<TValue>(ReadOnlySpan<byte> utf8Json, JsonTypeInfo<TValue> jsonTypeInfo) => ...; public static TValue? Deserialize<TValue>(string json, JsonTypeInfo<TValue> jsonTypeInfo) => ...; public static TValue? Deserialize<TValue>(ReadOnlySpan<char> json, JsonTypeInfo<TValue> jsonTypeInfo) => ...; public static TValue? Deserialize<TValue>(ref Utf8JsonReader reader, JsonTypeInfo<TValue> jsonTypeInfo) => ...; public static string Serialize(object? value, Type inputType, JsonSerializerContext context) => ...; public static void Serialize(Utf8JsonWriter writer, object? value, Type inputType, JsonSerializerContext context) { } public static Task SerializeAsync(Stream utf8Json, object? value, Type inputType, JsonSerializerContext context, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static Task SerializeAsync<TValue>(Stream utf8Json, TValue value, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static byte[] SerializeToUtf8Bytes(object? value, Type inputType, JsonSerializerContext context) => ...; public static byte[] SerializeToUtf8Bytes<TValue>(TValue value, JsonTypeInfo<TValue> jsonTypeInfo) => ...; public static void Serialize<TValue>(Utf8JsonWriter writer, TValue value, JsonTypeInfo<TValue> jsonTypeInfo) { } public static string Serialize<TValue>(TValue value, JsonTypeInfo<TValue> jsonTypeInfo) => ...; } }
برای معرفی تنظیمات serialization و deserialization، برای مثال تهیهی خروجیهای CamelCase، میتوان از ویژگی JsonSourceGenerationOptions به صورت زیر استفاده کرد:
using System.Text.Json.Serialization; namespace Test { [JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)] [JsonSerializable(typeof(Person))] internal partial class MyJsonContext : JsonSerializerContext { } }
string json = JsonSerializer.Serialize(person, MyJsonContext.Default.Person); Person person = JsonSerializer.Deserialize(json, MyJsonContext.Default.Person);
روش استفاده از JSON Source generators در برنامههای ASP.NET Core
در این نوع برنامهها، JsonSerializerContextها را میتوان توسط متد AddContext به صورت زیر به تنظیمات JSON برنامه معرفی کرد:
services.AddControllers().AddJsonOptions(options => options.AddContext<MyJsonContext>());
روش استفاده از JSON Source generators در برنامههای Blazor
البته در اینجا بیشتر منظور امکان استفادهی از آنها توسط HttpClient است که به صورت زیر توسط متد GetFromJsonAsync واقع در فضای نام System.Net.Http.Json، میسر شدهاست:
[JsonSerializable(typeof(WeatherForecast[]))] internal partial class MyJsonContext : JsonSerializerContext { } @code { private WeatherForecast[] forecasts; private static JsonSerializerOptions Options = new(JsonSerializerDefaults.Web); private static MyJsonContext Context = new MyJsonContext(Options); protected override async Task OnInitializedAsync() { forecasts = await Http.GetFromJsonAsync("sample-data/weather.json", Context.WeatherForecastArray); } }
namespace System.Net.Http.Json { public static partial class HttpClientJsonExtensions { public static Task<object?> GetFromJsonAsync(this HttpClient client, string? requestUri, Type type, JsonSerializerContext context, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static Task<object?> GetFromJsonAsync(this HttpClient client, System.Uri? requestUri, Type type, JsonSerializerContext context, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static Task<TValue?> GetFromJsonAsync<TValue>(this HttpClient client, string? requestUri, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static Task<TValue?> GetFromJsonAsync<TValue>(this HttpClient client, System.Uri? requestUri, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static Task<HttpResponseMessage> PostAsJsonAsync<TValue>(this HttpClient client, string? requestUri, TValue value, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static Task<HttpResponseMessage> PostAsJsonAsync<TValue>(this HttpClient client, System.Uri? requestUri, TValue value, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static Task<HttpResponseMessage> PutAsJsonAsync<TValue>(this HttpClient client, string? requestUri, TValue value, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static Task<HttpResponseMessage> PutAsJsonAsync<TValue>(this HttpClient client, System.Uri? requestUri, TValue value, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...; } public static partial class HttpContentJsonExtensions { public static Task<object?> ReadFromJsonAsync(this HttpContent content, Type type, JsonSerializerContext context, CancellationToken cancellationToken = default(CancellationToken)) => ...; public static Task<T?> ReadFromJsonAsync<T>(this HttpContent content, JsonTypeInfo<TValue> jsonTypeInfo, CancellationToken cancellationToken = default(CancellationToken)) => ...; } }