معرفی System.Text.Json در NET Core 3.0.
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: شش دقیقه

معروفترین کتابخانه‌ی کار با JSON در دات نت، Json.NET است که این روزها، جزء جدایی ناپذیر حداقل، تمام برنامه‌های وب مبتنی بر دات نت می‌باشد. برای مثال ASP.NET Core 2x/1x و همچنین ASP.NET Web API پیش از NET Core.، به صورت پیش‌فرض از این کتابخانه برای کار با JSON استفاده می‌کنند. این کتابخانه 10 سال پیش ایجاد شد و در طول زمان، قابلیت‌های زیادی به آن اضافه شده‌است. همین حجم بالای کار صورت گرفته سبب شده‌است که برای مثال شروع به استفاده‌ی از <Span<T در آن برای بالابردن کارآیی، بسیار مشکل شده و نیاز به تغییرات اساسی در آن داشته باشد. به همین جهت خود تیم CoreFX دات نت Core گزینه‌ی دیگری را برای کار با JSON در فضای نام جدید System.Text.Json ارائه داده‌است که برای کار با آن نیاز به نصب وابستگی ثالثی نیست و همچنین کارآیی آن به علت استفاده‌ی از ویژگی‌های جدید زبان، مانند ref struct و Span، به طور میانگین دو برابر کتابخانه‌ی Json.NET است. برای مثال استفاده‌ی از string (حالت پیش‌فرض کتابخانه‌ی Json.NET) یعنی کار با رشته‌هایی از نوع UTF-16؛ اما کار با Span، امکان دسترسی مستقیم به رشته‌هایی از نوع UTF-8 را میسر می‌کند که نیازی به تبدیل به رشته‌هایی از نوع UTF-16 را ندارند.


ASP.NET Core 3x دیگر به صورت پیش‌فرض به همراه Json.NET ارائه نمی‌شود

در برنامه‌های ASP.NET Core 3x، وابستگی ثالث Json.NET حذف شده‌است و از این پس هر نوع خروجی JSON آن، مانند بازگشت مقادیر مختلف از اکشن متدهای کنترلرها، به صورت خودکار در پشت صحنه از امکانات ارائه شده‌ی در System.Text.Json استفاده می‌کند و دیگر Json.NET، کتابخانه‌ی پیش‌فرض کار با JSON آن نیست. بنابراین برای کار با آن نیاز به تنظیم خاصی نیست. همینقدر که یک پروژه‌ی جدید ASP.NET Core 3x را ایجاد کنید، یعنی در حال استفاده‌ی از System.Text.Json هستید.


روش بازگشت به Json.NET در ASP.NET Core 3x

اگر به هر دلیلی هنوز نیاز به استفاده‌ی از کتابخانه‌ی Json.NET را دارید، آداپتور ویژه‌ی آن نیز تدارک دیده شده‌است. برای اینکار:
الف) ابتدا باید بسته‌ی نیوگت Microsoft.AspNetCore.Mvc.NewtonsoftJson را نصب کنید.
ب) سپس در کلاس Startup، باید این کتابخانه را به صورت یک سرویس جدید، با فراخوانی متد AddNewtonsoftJson، معرفی کرد:
 public void ConfigureServices(IServiceCollection services)
 {
     services.AddControllers()
            .AddNewtonsoftJson()
     // ...
}
یکی از دلایل بازگشت به Json.NET می‌تواند عدم پشتیبانی از OpenAPI / Swagger در حین کار با System.Text.Json باشد و این مورد قرار نیست در نگارش نهایی 3.0، حضور داشته باشد و انطباق با آن به نگارش‌های بعدی موکول شده‌است.


روش کار مستقیم با System.Text.Json

اگر در قسمتی از برنامه‌ی خود نیاز به کار مستقیم با اشیاء JSON را داشته باشید و یا حتی بخواهید از این قابلیت در برنامه‌های کنسول و یا کتابخانه‌ها نیز استفاده کنید، روش انتقال کدهایی که از Json.NET استفاده می‌کنند به System.Text.Json، به صورت زیر است:
public class Person
{
   public string FirstName { get; set; }
   public string LastName { get; set; }
   public DateTime? BirthDay { get; set; }
}
تبدیل رشته‌ی JSON حاوی اطلاعات شخص، به شیء متناظر با آن و یا حالت عکس آن:
using System;
using System.Text.Json.Serialization;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Person person = JsonSerializer.Parse<Person>(...);
            string json = JsonSerializer.ToString(person);
        }
    }
}
در اینجا از کلاس System.Text.Json.Serialization.JsonSerializer، روش کار با دو متد Parse را برای Deserialization و ToString را برای Serialization مشاهده می‌کنید.
کلاس JsonSerializer دارای overloadهای زیر برای کار با متدهای Parse و ToString است:
namespace System.Text.Json.Serialization
{
    public static class JsonSerializer
    {
        public static object Parse(ReadOnlySpan<byte> utf8Json, Type returnType, JsonSerializerOptions options = null);
        public static object Parse(string json, Type returnType, JsonSerializerOptions options = null);
        public static TValue Parse<TValue>(ReadOnlySpan<byte> utf8Json, JsonSerializerOptions options = null);
        public static TValue Parse<TValue>(string json, JsonSerializerOptions options = null);

        public static string ToString(object value, Type type, JsonSerializerOptions options = null);
        public static string ToString<TValue>(TValue value, JsonSerializerOptions options = null);
    }
}
یک نکته: کارآیی متد Parse با امضای ReadOnlySpan<byte> utf8Json، بیشتر است از نمونه‌ای که string json را می‌پذیرد. از این جهت که چون با آرایه‌ای از بایت‌های رشته‌ای از نوع UTF-8 کار می‌کند، نیاز به تبدیل به UTF-16 را مانند متدی که string را می‌پذیرد، ندارد. برای تولید آرایه‌ی بایت‌های utf8Json از روی یک شیء، می‌توانید از متد JsonSerializer.ToUtf8Bytes استفاده کنید و یا برای تولید آن از روی یک رشته، از متد Encoding.UTF8.GetBytes استفاده کنید.


سفارشی سازی JsonSerializer جدید

اگر به امضای متدهای Parse و ToString کلاس JsonSerializer دقت کنید، دارای یک پارامتر اختیاری از نوع JsonSerializerOptions نیز هستند که به صورت زیر تعریف شده‌است:
public sealed class JsonSerializerOptions
{
   public bool AllowTrailingCommas { get; set; }
   public int DefaultBufferSize { get; set; }
   public JsonNamingPolicy DictionaryKeyPolicy { get; set; }
   public bool IgnoreNullValues { get; set; }
   public bool IgnoreReadOnlyProperties { get; set; }
   public int MaxDepth { get; set; }
   public bool PropertyNameCaseInsensitive { get; set; }
   public JsonNamingPolicy PropertyNamingPolicy { get; set; }
   public JsonCommentHandling ReadCommentHandling { get; set; }
   public bool WriteIndented { get; set; }
}
برای نمونه معادل تنظیم NullValueHandling در Json.NET:
// Json.NET:
var settings = new JsonSerializerSettings
{
    NullValueHandling = NullValueHandling.Ignore
};
string json = JsonConvert.SerializeObject(person, settings);
اینبار توسط خاصیت IgnoreNullValues صورت می‌گیرد:
// JsonSerializer:
var options = new JsonSerializerOptions
{
    IgnoreNullValues = true
};
string json = JsonSerializer.ToString(person, options);

در برنامه‌های ASP.NET Core که این نوع متدها در پشت صحنه فراخوانی می‌شوند، روش تنظیم JsonSerializerOptions به صورت زیر است:
services.AddControllers()
   .AddJsonOptions(options => options.JsonSerializerOptions.WriteIndented = true);


نگاشت نام ویژه‌ی خواص در حین عملیات deserialization

در مثال فوق، فرض شده‌است که نام خاصیت BirthDay، دقیقا با اطلاعاتی که از رشته‌ی JSON دریافتی پردازش می‌شود، تطابق دارد. اگر این نام در اطلاعات دریافتی متفاوت است، می‌توان از ویژگی JsonPropertyName برای تعریف این نگاشت استفاده کرد:
[JsonPropertyName("birthdate")]
public DateTime? BirthDay { get; set; }
روش دیگر اینکار، برای نمونه تنظیم PropertyNamingPolicy به حالت CamelCase است:
var options = new JsonSerializerOptions
{
   PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
string json = JsonSerializer.ToString(person, options);
و یا اگر می‌خواهید حساسیت به بزرگی و کوچکی حروف را ندید بگیرید (مانند حالت پیش‌فرض JSON.NET) از تنظیم JsonSerializerOptions.PropertyNameCaseInsensitive استفاده کنید.

در این بین اگر نمی‌خواهید خاصیتی در عملیات serialization و یا برعکس آن پردازش شود، می‌توان از تعریف ویژگی [JsonIgnore] بر روی آن استفاده کرد.
  • #
    ‫۵ سال و ۱ ماه قبل، سه‌شنبه ۱۵ مرداد ۱۳۹۸، ساعت ۱۹:۵۴
    یک نکته‌ی تکمیلی
    در NET Core 3.0 Preview 7. امضای این کلاس‌ها و متدها به صورت زیر تغییر کرده‌است و متد Parse به Deserialize تبدیل شده‌است:
    namespace System.Text.Json
    {
        public static class JsonSerializer
        {
            public static object Deserialize(ReadOnlySpan<byte> utf8Json, Type returnType, JsonSerializerOptions options = null);
            public static object Deserialize(string json, Type returnType, JsonSerializerOptions options = null);
            public static TValue Deserialize<TValue>(ReadOnlySpan<byte> utf8Json, JsonSerializerOptions options = null);
            public static TValue Deserialize<TValue>(string json, JsonSerializerOptions options = null);
            public static object Deserialize(ref Utf8JsonReader reader, Type returnType, JsonSerializerOptions options = null);
            public static TValue Deserialize<TValue>(ref Utf8JsonReader reader, JsonSerializerOptions options = null);
            public static ValueTask<object> DeserializeAsync(Stream utf8Json, Type returnType, JsonSerializerOptions options = null, CancellationToken cancellationToken = default);
            public static ValueTask<TValue> DeserializeAsync<TValue>(Stream utf8Json, JsonSerializerOptions options = null, CancellationToken cancellationToken = default);
            public static string Serialize(object value, Type inputType, JsonSerializerOptions options = null);
            public static string Serialize<TValue>(TValue value, JsonSerializerOptions options = null);
            public static void Serialize(Utf8JsonWriter writer, object value, Type inputType, JsonSerializerOptions options = null);
            public static void Serialize<TValue>(Utf8JsonWriter writer, TValue value, JsonSerializerOptions options = null);
            public static Task SerializeAsync(Stream utf8Json, object value, Type inputType, JsonSerializerOptions options = null, CancellationToken cancellationToken = default);
            public static Task SerializeAsync<TValue>(Stream utf8Json, TValue value, JsonSerializerOptions options = null, CancellationToken cancellationToken = default);
            public static byte[] SerializeToUtf8Bytes(object value, Type inputType, JsonSerializerOptions options = null);
            public static byte[] SerializeToUtf8Bytes<TValue>(TValue value, JsonSerializerOptions options = null);
        }
    }
    همچنین JsonConverter نیز به تنظیمات آن اضافه شده‌است:
    namespace System.Text.Json
    {
        public sealed class JsonSerializerOptions
        {
            public JsonSerializerOptions();
    
            public bool AllowTrailingCommas { get; set; }
            public IList<JsonConverter> Converters { get; }
            public int DefaultBufferSize { get; set; }
            public JsonNamingPolicy DictionaryKeyPolicy { get; set; }
            public bool IgnoreNullValues { get; set; }
            public bool IgnoreReadOnlyProperties { get; set; }
            public int MaxDepth { get; set; }
            public bool PropertyNameCaseInsensitive { get; set; }
            public JsonNamingPolicy PropertyNamingPolicy { get; set; }
            public JsonCommentHandling ReadCommentHandling { get; set; }
            public bool WriteIndented { get; set; }
    
            public JsonConverter GetConverter(Type typeToConvert);
        }
    }
    با این تعریف:
    namespace System.Text.Json.Serialization
    {
        public abstract class JsonConverter
        {
            public abstract bool CanConvert(Type typeToConvert);
        }
    }
  • #
    ‫۵ سال قبل، دوشنبه ۲۸ مرداد ۱۳۹۸، ساعت ۱۳:۵۷
    یک نکته‌ی تکمیلی: نوشتن تبدیلگرهای نوع‌ها برای System.Text.Json

    System.Text.Json، در حال حاضر از مفهومی به نام type coercion/inference، پشتیبانی نمی‌کند. type coercion یعنی تبدیل یک مقدار، به مقداری دیگر که به صورت مستقیم قابل انتساب به یکدیگر نیستند. برای مثال اگر رشته‌ی "true" را درنظر بگیریم، قابلیت انتساب به یک خاصیت از نوع bool را ندارد. برای یک چنین مواردی در این API جدید، باید تبدیلگر نوشت.
    یک مثال:
    using System.Collections.Generic;
    using System.Text.Json;
    
    namespace JsonTests
    {
        public class Product
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public bool IsInStock { get; set; }
        }
    
        class Program
        {
            static void Main(string[] args)
            {            
             var products = JsonSerializer.Deserialize<List<Product>>("[{\"Id\":1026,\"Name\":\"P1\",\"IsInStock\":\"false\"}]");
            }
        }
    }
    در این مثال، خاصیت IsInStock از نوع bool است، اما مقداری را که باید از طریق متد Deserialize دریافت کنیم، یک رشته‌ی bool ای است که قابل انتساب به bool نیست. در این حالت اگر برنامه را اجرا کنیم به استثنای زیر خواهیم رسید:
    An unhandled exception of type 'System.Text.Json.JsonException' occurred in System.Text.Json.dll
    Inner exceptions found, see $exception in variables window for more details.
    Innermost exception System.InvalidOperationException : Cannot get the value of a token type 'String' as a boolean.
    برای رفع این مشکل، می‌توان تبدیلگر زیر را تدارک دید:
        public class BooleanConverter : JsonConverter<bool>
        {
            public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
            {
                var value = reader.GetString();
                if (value.Equals("true", StringComparison.OrdinalIgnoreCase)
                     || value.Equals("yes", StringComparison.OrdinalIgnoreCase)
                     || value.Equals("1", StringComparison.Ordinal))
                {
                    return true;
                }
    
                if (value.Equals("false", StringComparison.OrdinalIgnoreCase)
                     || value.Equals("no", StringComparison.OrdinalIgnoreCase)
                     || value.Equals("0", StringComparison.Ordinal))
                {
                    return false;
                }
    
                throw new NotSupportedException($"`{value}` can't be converted to `bool`.");
            }
    
            public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options)
            {
                switch (value)
                {
                    case true:
                        writer.WriteStringValue("true");
                        break;
                    case false:
                        writer.WriteStringValue("false");
                        break;
                }
            }
        }
    برای نوشتن یک تبدیلگر bool، کلاس مرتبط، باید <JsonConverter<bool را پیاده سازی کند. کلاس JsonConverter نیز به صورت زیر تعریف شده‌است:
        public abstract class JsonConverter<T> : JsonConverter
        {
            protected internal JsonConverter();
    
            public override bool CanConvert(Type typeToConvert);
            public abstract T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options);
            public abstract void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options);
        }
    و پیاده سازی دو متد Read و Write آن الزامی است.
    در متد Read آن، مقدار رشته‌ای دریافت شده‌ی از منبع داده، در اختیار ما قرار می‌گیرد. سپس باید بر اساس این مقدار، مقدار متناظری را از نوع T که در اینجا bool است، بازگشت دهیم. برای مثال اگر یکی از مقادیر رشته‌ای true ،yes و 1 را دریافت کردیم، بجای آن true را بازگشت می‌دهیم.

    اکنون برای استفاده‌ی از آن خواهیم داشت:
    var options = new JsonSerializerOptions();
    options.Converters.Add(new BooleanConverter());
    var products = JsonSerializer.Deserialize<List<Product>>(
       "[{\"Id\":1026,\"Name\":\"P1\",\"IsInStock\":\"false\"}]",
       options);
    و یا روش دیگر انجام اینکار، استفاده از ویژگی JsonConverter، برای معرفی تبدیلگر تهیه شده‌است:
    [JsonConverter(typeof(BooleanConverter))]
    public bool IsInStock { get; set; }

    از این تبدیلگر برای حالت Serialize نیز می‌توان استفاده کرد:
    var options  = new JsonSerializerOptions() {WriteIndented = true };
    options.Converters.Add(new BooleanConverter());
    var data = JsonSerializer.Serialize<List<Product>>(productList, options);
  • #
    ‫۵ سال قبل، جمعه ۱۵ شهریور ۱۳۹۸، ساعت ۱۴:۵۶
    یک نکته‌ی تکمیلی: استفاده از System.Text.Json در ASP.NET Core 3.0 و از کار افتادن تعدادی از اکشن متدها

    فرض کنید مدلی را به این صورت تعریف کرده‌اید:
    public class ModelIdViewModel
    {
       public string Id { set; get; }
    }
    و اکشن متدی که آن‌را دریافت می‌کند، به این نحو تعریف شده‌است:
    public async Task<IActionResult> RenderRole([FromBody]ModelIdViewModel model)

    در سمت کلاینت نیز اطلاعات Ajax ای متناظر با آن‌را به صورت زیر ارسال می‌کنید:
    data: JSON.stringify({ "id": 1 }),
    contentType: "application/json; charset=utf-8",
    dataType: "json",
    این اکشن متد تا نگارش 2.2، بدون مشکل کار می‌کرد. اما اکنون در نگارش 3، مقدار model آن نال شده‌است.
    برای دیباگ آن اگر قطعه کد زیر را اضافه کنیم:
    public async Task<IActionResult> RenderRole([FromBody]ModelIdViewModel model)
    {
       if (!ModelState.IsValid)
       {
          return BadRequest(ModelState);
       }
    یک چنین خروجی در قسمت network ابزارهای توسعه دهندگان مرورگر، ظاهر می‌شود:
     {"$.id":["The JSON value could not be converted to System.String. Path: $.id | LineNumber: 0 | BytePositionInLine: 7."]}
    عنوان می‌کند که مقدار id دریافتی را نمی‌تواند به string تبدیل کند.

    برای رفع این مشکل، فقط کافی است نوع Id را در model به int تبدیل کرد:
    public class ModelIdViewModel
    {
       public int Id { set; get; }
    }
    به عبارتی System.Text.Json جدید، همانند Newtonsoft.Json قبلی، سعی نمی‌کند int دریافتی از کاربر را به string درخواستی در model تبدیل کند. نوع‌ها حتما باید تناظر داشته باشند.
  • #
    ‫۵ سال قبل، جمعه ۱۵ شهریور ۱۳۹۸، ساعت ۱۷:۱۱
    JsonSerializerOptions پیش‌فرض در ASP.NET Core 3.0


  • #
    ‫۴ سال و ۱۰ ماه قبل، پنجشنبه ۲۵ مهر ۱۳۹۸، ساعت ۱۲:۳۳
    امکانش هست برای JsonConverter‌ها Dependency تعریف کرد؟
    مثلا میخوام داخل JsonConverter از IDataProtectionProvider استفاده کنم. 
    • #
      ‫۴ سال و ۱۰ ماه قبل، پنجشنبه ۲۵ مهر ۱۳۹۸، ساعت ۱۳:۴۲
      یک نکته‌ی تکمیلی: روش تزریق وابستگی‌ها به تنظیمات MvcBuilder در متد ConfigureServices

      - روش دیگر آن به کمک IConfigureOptions و معرفی آن به سیستم تزریق وابستگی‌ها است تا وابستگی‌های آن‌را به صورت خودکار تامین کند:
          public class AppJsonOptions : IConfigureOptions<JsonOptions>
          {
              private readonly IHttpContextAccessor _accessor;
      
              public AppJsonOptions(IHttpContextAccessor accessor)
              {
                  _accessor = accessor;
              }
      
              public virtual void Configure(JsonOptions options)
              {
                  options.JsonSerializerOptions.Converters.Add(new BooleanConverter(_accessor));
              }
          }

      پس از این تعریف، نحوه‌ی معرفی آن به سیستم به صورت زیر خواهد بود:
          public void ConfigureServices(IServiceCollection services)
          {
             services.AddHttpContextAccessor();
             services.AddSingleton<IConfigureOptions<JsonOptions>, AppJsonOptions>();

      و Converter تعریف شده هم به این نحو می‌تواند به وابستگی‌ها دسترسی پیدا کند:
          public class BooleanConverter : JsonConverter<bool>
          {
              private readonly IHttpContextAccessor _accessor;
      
              public BooleanConverter(IHttpContextAccessor accessor)
              {
                  _accessor = accessor;
              }
      
              public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
              {
                  var testService = _accessor.HttpContext.RequestServices.GetRequiredService<TestService>();
      
                  // ...
                  throw new NotSupportedException();
              }
      
              public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options)
              {
              }
          }

      علت استفاده‌ی از IHttpContextAccessor در اینجا این است که چون IConfigureOptions به صورت Singleton ثبت می‌شود، بنابراین فقط سرویس‌هایی با طول عمر Singleton را هم می‌توان به آن تزریق کرد؛ اما RequestServices.GetRequiredService این محدودیت را ندارد.
      • #
        ‫۴ سال و ۱۰ ماه قبل، جمعه ۳ آبان ۱۳۹۸، ساعت ۰۴:۳۱
        با سلام؛ من با reactjs وقتی فرم را به سمت سرور ارسال میکنم، اگر input با نوع number را در قسمت HTML داشته باشم، آن را به صورت string میفرستد و من مجبورم در سمت کلاینت اون رو تبدیل به int کنم وبعد بفرستم و میخواستم با همین روشی که گفتید، یک کلاس بنویسم که اون رو بالای فیلدم تعریف کنم و نیاز به اون تبدیله نباشه ...
           public class IntConverter : JsonConverter<int> {
                public override int Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
                    var value = reader.GetString ();
                    if (int.TryParse (value, out _))
                        return int.Parse (value);
                    throw new NotSupportedException ($"`{value}` can't be converted to `int`.");
                }
        
                public override void Write (Utf8JsonWriter writer, int value, JsonSerializerOptions options) {
                    writer.WriteNumberValue (value);
                }
            }
        • #
          ‫۴ سال و ۱۰ ماه قبل، یکشنبه ۵ آبان ۱۳۹۸، ساعت ۱۵:۴۷
          قسمت
          if (int.TryParse (value, out _))
             return int.Parse (value);
          به این نحو قابل بازنویسی است:
          if (int.TryParse (value, out var result))
             return result;
        • #
          ‫۴ سال و ۱۰ ماه قبل، یکشنبه ۵ آبان ۱۳۹۸، ساعت ۱۶:۰۳
          روشی که خودشان برای این نوع تبدیلات (تبدیل عدد ارسالی به صورت رشته‌ای یا همان عدد داخل "") پیشنهاد کرده‌اند به این صورت هست:
          using System;
          using System.Buffers;
          using System.Buffers.Text;
          using System.Text.Json;
          using System.Text.Json.Serialization;
          
          namespace Test
          {
              public class LongToStringConverter : JsonConverter<long>
              {
                  public override long Read(ref Utf8JsonReader reader, Type type, JsonSerializerOptions options)
                  {
                      if (reader.TokenType == JsonTokenType.String)
                      {
                          ReadOnlySpan<byte> span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
                          if (Utf8Parser.TryParse(span, out long number, out int bytesConsumed) && span.Length == bytesConsumed)
                          {
                              return number;
                          }
          
                          if (long.TryParse(reader.GetString(), out number))
                          {
                              return number;
                          }
                      }
          
                      return reader.GetInt64();
                  }
          
                  public override void Write(Utf8JsonWriter writer, long value, JsonSerializerOptions options)
                  {
                      writer.WriteStringValue(value.ToString());
                  }
              }
          }
          در نگارش NET 5.0. به نظر قرار هست گزینه‌ای شبیه به options.DeserializeQuotedNumbers == true برای مدیریت این نوع موارد اضافه شود.
  • #
    ‫۴ سال و ۴ ماه قبل، دوشنبه ۸ اردیبهشت ۱۳۹۹، ساعت ۱۷:۵۸
    یک نکته‌ی تکمیلی: تبدیل enum در سمت سرور
    اگر توسط درخواست‌های Ajax ای مقدار یک enum را به صورت رشته‌ای ارسال می‌کنید، در سمت سرور چنین خطایی را دریافت خواهید کرد:
    {"$.language":["The JSON value could not be converted to DNTCaptcha.Core.Providers.Language. Path: $.language | LineNumber: 0 | BytePositionInLine: 99."]}
    این خطا را هم با فعالسازی ویژگی [ApiController] بر روی کنترلر خود می‌توانید مشاهده کنید و یا توسط بازگشت ModelState در اکشن متد خود به صورت زیر:
    if (model == null || !ModelState.IsValid)
    {
      return BadRequest(ModelState);
    }
    برای رفع این مشکل باید تنظیم زیر را به کلاس آغازین برنامه اضافه کرد:
    services.AddControllers().AddJsonOptions(opt =>
    {
       opt.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
    });
    تبدیلگر آن نیز توکار بوده و در فضای نام System.Text.Json تعریف شده‌است.
  • #
    ‫۳ سال و ۹ ماه قبل، پنجشنبه ۲۹ آبان ۱۳۹۹، ساعت ۲۳:۳۰
    یک نکته‌ی تکمیلی
    روش تبدیل/سریالایز سریع یک شیء، به آرایه‌ای از بایت‌های UTF8:
    var resultBytes = JsonSerializer.SerializeToUtf8Bytes(data,
                        new JsonSerializerOptions { WriteIndented = false, IgnoreNullValues = true });
    و برعکس، تبدیل آرایه‌ای از بایت‌های UTF8 که به روش فوق سریالایز شده به شیء اصلی:
    var data = JsonSerializer.Deserialize<T>(new ReadOnlySpan<byte>(resultBytes));
    • #
      ‫۳ سال و ۸ ماه قبل، سه‌شنبه ۹ دی ۱۳۹۹، ساعت ۱۸:۲۴
      سلام. توی net5 کانورترش اگر عددی داخل کوتیشن باشه و مدلمون هم به صورت عددی باشه (byte, int,...) بایند میکنه ولی اگر داخل کوتیشن خالی باشه دیگه بایند نمیشه مثلاً به byte? (نالیبل)
      برای این موضوع راه حلی هست؟
      public class SampleModel
      {
      public byte? Value { get; set; }
      }
      
      var options = new JsonSerializerOptions();
      options.NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString;
      
      var body = @"{""Value"":""""}";
      var model = JsonSerializer.Deserialize<SampleModel>(body, options);

      • #
        ‫۳ سال و ۸ ماه قبل، سه‌شنبه ۹ دی ۱۳۹۹، ساعت ۲۳:۴۱
        نمایش رسمی null در جاوا اسکریپت به این صورت است:
        { "Value": null }
        بنابراین اگر چنین اطلاعاتی به سمت سرور ارسال شود، به درستی پردازش خواهد شد؛ اگرنه باید از تبدیلگر زیر استفاده کنید:
            public class StringJsonConverter : JsonConverter<byte?>
            {
                public override byte? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
                {
                    return reader.TokenType switch
                    {
                        JsonTokenType.Number => reader.GetByte(),
                        JsonTokenType.Null => null,
                        JsonTokenType.String =>
                            byte.TryParse(reader.GetString(), NumberStyles.Number, CultureInfo.InvariantCulture, out var parsed) ? parsed : (byte?)null,
                        _ => throw new ArgumentOutOfRangeException(nameof(reader), reader.TokenType, "Cannot parse unexpected JSON token type.")
                    };
                }
        
                public override void Write(Utf8JsonWriter writer, byte? value, JsonSerializerOptions options)
                {
                    if (value.HasValue)
                    {
                        writer.WriteNumberValue(value.Value);
                    }
                    else
                    {
                        writer.WriteNullValue();
                    }
                }
            }
  • #
    ‫۳ سال و ۷ ماه قبل، چهارشنبه ۲۴ دی ۱۳۹۹، ساعت ۱۵:۲۷
    اگر خاصیتی را به صورت زیر تعریف کرده باشید:
    public string Summary { get; private set; }
    در نگارش 5، باید حتما به همراه ویژگی [JsonInclude] هم باشد تا به درستی serialize و deserialize شود. همچنین ذکر private set هم ضروری است. نگارش 5، خواصی را که فقط به صورت get دار یا read-only تنظیم شده باشند، deserialize نمی‌کند.
  • #
    ‫۳ سال و ۷ ماه قبل، پنجشنبه ۲۵ دی ۱۳۹۹، ساعت ۱۷:۴۲
    یک نکته‌ی تکمیلی: خروجی یونیکد در JSON نهایی
    اگر مقادیر خواص شما دارای حروف یونیکد باشند، در خروجی نهایی ارائه شده، این حروف escape می‌شوند. یعنی به صورت برای مثال u003E\ نمایش داده خواهند شد. البته این مساله هیچ تاثیری را بر روی تبدیل این خروجی JSON، به شیء ابتدایی ندارد. فقط هدف آن‌ها بالا بردن امنیت قطعه string تولید شده جهت «درج خام» آن در یک صفحه‌است. برای نمونه اگر قصد ندارید این رشته‌ی نهایی JSON تولید شده را در بین تگ‌های <script> یک صفحه‌ی HTML به صورت مستقیما قرار دهید، می‌توانید این escape پیش‌فرض را غیرفعال کنید:
    options = new JsonSerializerOptions
    {
        Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
        WriteIndented = true
    };
    jsonString = JsonSerializer.Serialize(weatherForecast, options);
    و یا حتی اگر می‌خواهید باز هم یکسری حروف خاص ASCII را به صورت escape شده داشته باشید، اما حروف یونیکد را به صورت معمولی مشاهده کنید، می‌توانید از روش زیر استفاده کنید:
    var options = new JsonSerializerOptions
    {
        Encoder = JavaScriptEncoder.Create(UnicodeRanges.All)
    };