Önceki yazılarımdan birinde .NET Core 3.0 ile beraber gelen yeni System.Text.Json
API'larından bahsetmiştim. O yazıyı, bu API'ların mevcutta kullandığımız Newtonsoft.Json
kütüphanesine göre oldukça yeni olduğundan bu nedenle de özellik bakımından arada farklar olabileceğinden, .NET 5.0 ile beraber bu farkın biraz daha kapanacağından bahsederek sonlandırmıştım. Bu yazının konusu da System.Text.Json
API'larına .NET 5.0 ile beraber gelen yenilikler. Vakit kaybetmeden hızlıca yenilikleri inceleyelim.
String'den farklı bir tipte key'e sahip Dictinary desteği
.NET 5.0 öncesinde key tipi stringden farklı olan dictionaryleri serialize & deserialize etmek istediğimizde NotSupportedException
alıyorduk. .NET 5.0 ile beraber artık bu limitasyon ortadan kalkıyor.
Örnek yaparsak,
var dictionary = new Dictionary<int, string>() { { 34, "istanbul" }, { 6,"Ankara" }, { 1,"Adana"} }; var json = JsonSerializer.Serialize(dictionary); //json: {"34":"istanbul","6":"Ankara","1":"Adana"} var dictionary2 = JsonSerializer.Deserialize<Dictionary<int, string>>(json);
Fieldlar için serialize & deserialize desteği
.NET 5.0 öncesindeki eksikliklerden biri de fieldların serialize & deserialize edilmesi eksikliğiydi. .NET 5.0 ile beraber artık fieldlarında serialize & deserialize edilmesini sağlayabiliyoruz. Ancak bu özellik default olarak açık değil. Bu yüzden JsonSerializerOptions
içerisindeki IncludeFields
propertysini true'ya çekmek gerekiyor.
Örnek yaparsak,
var plaka = new Plaka { Kod = 1, Sehir = "Adana" }; var options = new JsonSerializerOptions { IncludeFields = true }; var json = JsonSerializer.Serialize(plaka, options); //json: {"Kod":1,"Sehir":"Adana"} var plaka2 = JsonSerializer.Deserialize<Plaka>(json, options); public class Plaka { public int Kod; public string Sehir; }
Immutable class & structlar için serialize & deserialize desteği
Immutable olarak tanımlanan class ve structların serialize & deserialize desteği de yine bir önceki versiyondaki eksikliklerden biriydi. Aşağıdaki gibi bir classımız olduğunu düşünelim.
public class Plaka { public int Kod { get; } public string Sehir { get; } public Plaka(int kod, string sehir) { Kod = kod; Sehir = sehir; } public Plaka() { } }
JsonSerializer bir jsonı deserialize etmek istediğinde parametresiz constructorı kullanacağı için hiçbir zaman doğru bir şekilde deserialize işlemi yapamayacaktı. .NET 5.0 ile beraber gelen JsonConstructor
attribute'ü ile beraber JsonSerializer'a kullanması gereken constructorı bildirip doğru bir şekilde deserialize etmesini sağlayabiliyoruz.
var plaka = new Plaka(1, "Adana"); var json = JsonSerializer.Serialize(plaka); //json: {"Kod":1,"Sehir":"Adana"} var plaka2 = JsonSerializer.Deserialize<Plaka>(json); public class Plaka { public int Kod { get; } public string Sehir { get; } [JsonConstructor] public Plaka(int kod, string sehir) { Kod = kod; Sehir = sehir; } public Plaka() { } }
Eğer class
içerisinde sadece bir tane parametreli constructor varsa JsonSerializer JsonConstructor
attribute'ünü belirtmeye gerek kalmadan otomatik olarak o constructorı kullanır.
public class Plaka { public int Kod { get; } public string Sehir { get; } public Plaka(int kod, string sehir) { Kod = kod; Sehir = sehir; } }
Structlarda
ise her zaman parametresiz constructor bulunduğu için structlar için mutlaka yukarıda bahsettiğimiz attribute'ü kullanmak zorundayız.
Peki JsonSerializer parametreli constructor içerisinde hangi parametreye hangi değeri göndereceğini nereden biliyor diye düşünebilirsiniz. JsonSerializer property adını otomatik olarak camelCase'e
çeviriyor ve constructor içerisinde o parametreyi arıyor. Eğer bulursa ilgili parametreye ilgili değeri gönderiyor. Bulamadığı durumlarda InvalidOperationException
alabilirsiniz.
Reference Handling
.NET 5.0 öncesinde circular referansları yönetmede kullanılan tek yöntem bir MaxDepth
belirtip, bu limit geçildiğinde exception fırlatılmasıydı. .NET 5.0 ile beraber JsonSerializerOptions
içerisine eklenen ReferenceHandling
propertysini ReferenceHandling.Preserve
set ettiğimizde artık serializer $id, $values ve $ref
gibi metadata propertylerini de json içerisine yazarak arrayler, collectionlar vb.. gibi yerlerde referans vermek için kullanabilmekte.
var employee = new Employee { Name = "osman", Reports = new List<Employee>() }; var employee2 = new Employee { Name = "mehmet", Manager = employee }; var employee3 = new Employee { Name = "ahmet", Manager = employee }; employee.Reports.AddRange(new[] { employee2, employee3 }); var json = JsonSerializer.Serialize(employee, new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.Preserve }); public class Employee { public string Name { get; set; } public Employee Manager { get; set; } public List<Employee> Reports { get; set; } }
JsonSerializer'dan çıkan JSON
{ "$id": "1", "Name": "osman", "Manager": null, "Reports": { "$id": "2", "$values": [ { "$id": "3", "Name": "mehmet", "Manager": { "$ref": "1" }, "Reports": null }, { "$id": "4", "Name": "ahmet", "Manager": { "$ref": "1" }, "Reports": null } ] } }
NumberHandling
Bazı durumlarda JSON içerisinde number değerler tırnak içerisinde yani string olarak yer alabilmekte. Böyle durumlarda bu alanlar deserialize edilirken int, double gibi değişkenlere deserialize edilemiyordu. .NET 5.0 ile beraber JsonSerializerOptions
içerisindeki NumberHandling
propertysi ile bu alanların serialize ve deserialize edilirkenki davranışlarını kontrol edebiliyoruz.
JsonNumberHandling
enumı içerisinde kullanabileceğimiz dört değer bulunmakta.
Değer | Açıklaması |
---|---|
Strict | Number valuelar json içerisinde number olarak tanımlanan alanlardan okunur ve yazılır.(Tırnak olmadan.) |
AllowReadingFromString | Number alanlar JSON içerisinde hem number alanlardan hem de string olarak belirtilen alanlardan okunabilir. |
WriteAsString | Number alanlar JSON string olarak yazılır. |
AllowNamedFloatingPointLiterals | "NaN", "Infinity", ve "-Infinity" gibi floating point constantları string olarak okunup Single.NaN gibi değerlere set edilebilir. Aynı zamanda bu constant değerler JSON'a serialize edilirken string olarak serialize edilir. |
Serialize İşlemi Sırasında Default Değerleri Yoksayma
.NET 5.0 ile beraber default değere sahip olan alanların serialize edilirken JSON içerisinde yer almamasını sağlayabilirsiniz. Bunun için JsonSerializerOptions
içerisindeki DefaultIgnoreCondition
propertysini kullanabilirsiniz. Bu propertynin alabileceği değerler ise şu şekilde.
Değer | Açıklaması |
---|---|
Never | Property her zaman serialize ve deserialize edilir. |
Always | Property her zaman ignore edilir. |
WhenWritingDefault | Eğer property default değere eşitse serialize edilirken ignore edilir. |
WhenWritingNull | Eğer property null ise ignore edilir. Bu alan reference type değişkenler ve propertyler için kullanılır. |
HttpClient JSON Extension Metotları
.NET 5.0 ile beraber HttpClient
içerisine JSON extension metotları geliyor. Metotların ne iş yaptığına zaten çok değinmeyeceğim. İsimlerinden oldukça anlaşılır durumda. Extension metotlar System.Net.Http.Json
namespace'i içerisinde bulunmakta.
Metotların isimleri şunlar,
- GetFromJsonAsync
- PostAsJsonAsync
- PutAsJsonAsync
C# 9.0 Record Desteği
C# 9.0 ile beraber gelen en önemli yeniliklerden biri de recordlar
. .NET 5.0 ile beraber recordların serialize & deserialize edilmesi de mümkün.
Performans İyileştirmeleri
.NET 5.0 ile beraber mevcut JsonSerializer
API'larının performanslarında da iyileştirmeler yapılmış durumda. Özellikle büyük collectionların serialize & deserialize edilmesinde güzel iyileştirmeler bulunmakta. Bunlarla ilgili issueları incelemek için aşağıdaki linkleri kullanabilirsiniz.
- New serializer converter model for objects and collections
- Is it possible to optimize JSON serialization any further?
- Improve deserialization perf for case-insensitive and missing-property cases
- Use Sse2 instrinsics to make NeedsEscaping check faster for large JSON strings
Bir sonraki yazıda görüşmek üzere