System.Text.Json vs Newtonsoft.Json

.NET'te JSON işlemek için iki kütüphanenin performans, özellik ve uyumluluk karşılaştırması yapılır. JsonConverter yazarak özel serileştirme senaryoları çözülür.

System.Text.Json vs Newtonsoft.Json

System.Text.Json vs Newtonsoft.Json: Performans, Özellikler ve Custom Converter Rehberi

.NET ekosisteminde JSON işleme denince akla iki dev gelir: Efsanevi Newtonsoft.Json (Json.NET) ve .NET Core 3.0 ile doğan yerli System.Text.Json (STJ). STJ, performans ve bellek tahsisi (alloc) açısından Newtonsoft'u açık ara geçerken, Newtonsoft hala bazı esnek özelliklerde (özellikle döngüsel referanslar, eski tip dönüşümleri) tahtını koruyor. Bu yazıda, hangi durumda hangisini seçeceğinizi, STJ ile nasıl custom converter yazacağınızı ve bilinen tuzakları ele alıyoruz.


1. Performans Karşılaştırması (Benchmark)

Aşağıdaki tablo, 10.000 kez küçük bir nesneyi serileştirip deserileştiren testin yaklaşık sonuçlarıdır (dotnet/benchmarks):

Ölçüm Newtonsoft.Json System.Text.Json Fark
Serileştirme (Önerilen) ~350 ns ~120 ns ~3x hızlı
Deserileştirme ~450 ns ~180 ns ~2.5x hızlı
Bellek Tahsisi (Ser.) ~1.2 KB ~0.4 KB ~3x daha az alloc
Başlangıç Isınma Yavaş (JIT) Hızlı (Source Generator ile daha da hızlı) STJ önde

STJ Neden Daha Hızlı?

  • Utf8JsonReader ve Utf8JsonWriter doğrudan UTF-8 baytları üzerinde çalışır, string dönüşümlerini en aza indirir.

  • Span<T> ve ref struct kullanarak ek bellek tahsisini neredeyse sıfırlar.

  • Reflection yerine derleme zamanında üretilen JsonSerializerContext (Source Generator) ile AOT dostudur.


2. Özellik Karşılaştırması (Kim Eksik?)

Özellik Newtonsoft.Json System.Text.Json
Döngüsel Referanslar (Circular References) ✅ PreserveReferencesHandling ile destekler ❌ Varsayılan yok, custom converter gerekir
Polimorfizm (Türetilmiş tipler) ✅ TypeNameHandling ile ⚠️ .NET 7+ ile [JsonDerivedType] desteği geldi
LINQ to JSON (JObject, JArray) ✅ (JToken) ❌ (Yok, yerine JsonNode .NET 6+ ile geldi)
Kamel-case / Pascal-case dönüşümü ✅ ContractResolver ile ✅ JsonNamingPolicy ile
Null değer yok sayma NullValueHandling.Ignore DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
Özel Tarih Formatları DateFormatString ile [JsonConverter] ile veya JsonSerializerOptions.Converters
Büyük Veri Akışı (Streaming) ❌ (Tümünü belleğe alır) ✅ JsonSerializer.DeserializeAsyncEnumerable ile
Source Generator / AOT Desteği ❌ ✅ JsonSerializerContext ile
F# / C# 9 Record Desteği ⚠️ (Kısmi) ✅ Tam destek

3. Sistem.Text.Json ile Custom Converter Yazmak (JsonConverter)

STJ'de özel serileştirme ihtiyaçları için JsonConverter<T> sınıfından türetmek gerekir. Örnek: DateTime'ı Unix timestamp (long) olarak serileştirmek.

csharp

public class UnixDateTimeConverter : JsonConverter<DateTime>
{
    // Unix epoch (01.01.1970)
    private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

    public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        // Okuyucudan long değer al (Unix timestamp)
        if (reader.TokenType != JsonTokenType.Number)
            throw new JsonException("Beklenen tip: Number (Unix timestamp)");

        var unixTime = reader.GetInt64();
        return Epoch.AddSeconds(unixTime);
    }

    public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
    {
        // DateTime'ı Unix timestamp'e çevir
        var unixTime = (long)(value.ToUniversalTime() - Epoch).TotalSeconds;
        writer.WriteNumberValue(unixTime);
    }
}

// Kullanım: Özelliğe Attribute ekleyerek
public class MyModel
{
    [JsonConverter(typeof(UnixDateTimeConverter))]
    public DateTime CreatedAt { get; set; }
}

// Veya global olarak ekleme:
var options = new JsonSerializerOptions();
options.Converters.Add(new UnixDateTimeConverter());
var json = JsonSerializer.Serialize(new MyModel { CreatedAt = DateTime.UtcNow }, options);

Daha Karmaşık Örnek: Polimorfik Serileştirme (Türetilmiş Sınıf)

Elimizde Animal base sınıfı ve Dog, Cat türevleri var. Serileştirirken tip bilgisini eklemek isteyelim:

csharp

[JsonConverter(typeof(AnimalConverter))]
public abstract class Animal { public string Name { get; set; } }
public class Dog : Animal { public int BarkVolume { get; set; } }
public class Cat : Animal { public int WhiskerLength { get; set; } }

public class AnimalConverter : JsonConverter<Animal>
{
    public override Animal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        using var doc = JsonDocument.ParseValue(ref reader);
        var root = doc.RootElement;
        var typeDiscriminator = root.GetProperty("Type").GetString();

        return typeDiscriminator switch
        {
            "Dog" => JsonSerializer.Deserialize<Dog>(root.GetRawText(), options),
            "Cat" => JsonSerializer.Deserialize<Cat>(root.GetRawText(), options),
            _ => throw new NotSupportedException($"Bilinmeyen tip: {typeDiscriminator}")
        };
    }

    public override void Write(Utf8JsonWriter writer, Animal value, JsonSerializerOptions options)
    {
        // Tip bilgisini ekleyerek yaz
        writer.WriteStartObject();
        writer.WriteString("Type", value.GetType().Name);
        foreach (var prop in value.GetType().GetProperties())
        {
            writer.WritePropertyName(prop.Name);
            JsonSerializer.Serialize(writer, prop.GetValue(value), prop.PropertyType, options);
        }
        writer.WriteEndObject();
    }
}

(Not: .NET 7+ ile bu iş için [JsonDerivedType] attribute'ü kullanmak çok daha kolaydır.)


4. System.Text.Json Source Generator (AOT ve Performans)

STJ'nin en büyük süper gücü Source Generator'dır. Reflection'ı devre dışı bırakarak, serileştirme kodunu derleme anında üretir. Bu, hem başlangıç (startup) performansını artırır hem de Native AOT derlemelerinde çalışmasını sağlar.

csharp

// 1. Kısmi bir context sınıfı oluştur:
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(MyModel))]
[JsonSerializable(typeof(List<MyModel>))]
public partial class MyJsonContext : JsonSerializerContext { }

// 2. Kullanım:
var options = new JsonSerializerOptions
{
    TypeInfoResolver = MyJsonContext.Default // Context'i kullan
};
var json = JsonSerializer.Serialize(myModel, typeof(MyModel), MyJsonContext.Default);

Performans Kazancı: Reflection tabanlı serileştirmeye göre ~2x daha hızlı ve sıfır ek tahsis (alloc). Ayrıca derleme zamanında hata yakalar (ör. serileştirilemeyen tip).


5. Ne Zaman Hangi Kütüphane?

Senaryo Tercih
Yeni proje, yüksek performans gerekiyor, AOT hedefleniyor System.Text.Json
MVC/WebAPI varsayılanı (ASP.NET Core 3.0+) System.Text.Json (zaten varsayılan)
Newtonsoft ile yazılmış eski kod tabanı, geçiş zor Newtonsoft.Json (ancak kademeli geçiş önerilir)
Döngüsel referanslar (circular refs) ile çalışıyorsunuz Newtonsoft.Json veya STJ + custom ref handling
Dinamik JSON (JObject, JArray) manipülasyonu Newtonsoft.Json (STJ'de JsonNode daha yeni ve sınırlı)
F# veya C# Record'lar ile çalışıyorsunuz System.Text.Json (tam uyum)
XML, BSON, JSON Patch gibi ek formatlar Newtonsoft.Json (ek paketlerle)

6. Bilinen Tuzaklar ve Çözümleri (STJ)

Tuzak Çözüm
DateTime offset bilgisi kaybolur DateTimeOffset kullanın veya [JsonConverter(typeof(JsonStringEnumConverter))] ile format belirtin.
Enum'lar sayı olarak serileşir [JsonConverter(typeof(JsonStringEnumConverter))] ekleyin veya options'ta Converters.Add(new JsonStringEnumConverter()).
Büyük JSON okuma yavaşlar JsonSerializer.DeserializeAsyncEnumerable kullanarak akış (stream) modunda okuyun.
Özel koleksiyonlar serileşmez Custom converter yazın veya IEnumerable<T> dönün.
IgnoreCondition çalışmıyor JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull ayarını unutmayın.

Sonuç:

System.Text.Json, yeni .NET projelerinin varsayılanı ve geleceğidir. Newtonsoft.Json ise hala güçlüdür ancak özellikle performans ve AOT gereksinimlerinde geride kalmıştır. Custom converter yazmak, STJ'nin eksik kaldığı noktalarda (tarih formatları, polimorfizm, döngü) size tam kontrol sağlar. Eğer yeni bir projeye başlıyorsanız, STJ'yi tercih edin; Source Generator ile taçlandırın. Eski projelerde ise Newtonsoft'tan STJ'ye geçiş için adım adım (önce DTO'ları, sonra tüm katmanı) dönüşüm yapabilirsiniz.

Tüm yazılar