GoSuda

Go 1.25 encoding/json/v2 davranış değişiklikleri ve v1 uyumluluk katmanının analizi

By lemonmint views ...

Go'nun encoding/json paketi v2, mevcut v1'in çeşitli dezavantajlarını (tutarsızlık, şaşırtıcı davranışlar, performans sorunları) gidermek için geliştirilmiş yeni bir uygulamadır. Bu özellik, goexperiment.jsonv2 derleme etiketi (build tag) aracılığıyla etkinleştirilen deneysel bir özelliktir.

En önemli nokta, v2 etkinleştirildiğinde v1'in, v2 uygulaması üzerinde v1'in davranışını taklit eden bir uyumluluk katmanı olarak çalışmasıdır. Bu, v2_v1.go dosyasındaki DefaultOptionsV1() fonksiyonu aracılığıyla gerçekleştirilir. Yani v2, v1'in davranışını tam olarak yeniden üretebilen seçenekler sunarken, aynı zamanda daha katı ve öngörülebilir yeni varsayılan davranışlar da önerir.

v2'nin başlıca hedefleri şunlardır:

  1. Doğruluk ve öngörülebilirliğin artırılması: Varsayılan olarak daha katı kurallar (ör. büyük/küçük harf duyarlılığı, yinelenen anahtarların yasaklanması) uygulayarak beklenmeyen davranışları azaltır.
  2. Performansın iyileştirilmesi: Ayrıştırma (parsing) ve kodlama (encoding) motorları, verimliliği artırmak için yeniden tasarlanmıştır.
  3. Esneklik ve denetimin genişletilmesi: Geliştiricilerin JSON işleme yöntemlerini ayrıntılı biçimde kontrol edebilmesi için kapsamlı bir seçenek sistemi sunulmuştur.

Başlıca Anlam/Davranış Farklılıkları

V1 ile v2 arasındaki davranış farklılıkları, v2_test.go dosyası temel alınarak madde madde özetlenmiştir.

1. Alan Adı Eşleştirme (Büyük/Küçük Harf Duyarlılığı)

  • v1 davranışı: JSON nesne üyelerini Go yapı alanlarına unmarshal ederken büyük/küçük harfe duyarsız (case-insensitive) eşleştirme yapar. Hem "FirstName" hem de "firstname", FirstName alanına eşlenir.
  • v2 davranışı: Varsayılan olarak büyük/küçük harfe duyarlı (case-sensitive) çalışır ve yalnızca tam olarak eşleşen alanları eşler.
  • Değişikliğin nedeni: Büyük/küçük harfe duyarsız eşleştirme, beklenmeyen davranışların kaynağı olabilir ve eşleşmeyen alanların işlenmesinde performans düşüşüne yol açar. v2, daha açık ve öngörülebilir davranışı varsayılan olarak benimsemiştir.
  • İlgili seçenekler: v2'de, json:"...,case:ignore" etiket seçeneği ile alan bazında büyük/küçük harf duyarsızlığı açıkça etkinleştirilebilir veya json.MatchCaseInsensitiveNames(true) seçeneği genel olarak uygulanabilir.

2. omitempty Etiket Seçeneğinin Anlamındaki Değişiklik

  • v1 davranışı: Alanları Go değerinin "boş (empty) durumuna" göre atlar. Burada "boş durum", false, 0, nil işaretçi/arayüz ve uzunluğu 0 olan dizi/dilim/harita/dizgi anlamına gelir.
  • v2 davranışı: Alanları kodlanmış JSON değerinin "boş durumuna" göre atlar. Yani null, "", {} veya [] olarak kodlanan değerler atlanır.
  • Değişikliğin nedeni: v1'deki tanım Go'nun tür sistemine bağımlıdır. v2 ise JSON tür sistemini temel alarak daha tutarlı bir davranış sunar. Örneğin v1'de bool türündeki false değeri atlanırken, v2'de false boş olmayan bir JSON değeri olduğu için atlanmaz. v2, v1'deki omitempty'nin 0 veya false değerlerine uygulanan davranışının yerini alabilmesi için omitzero seçeneğini ekler.
  • İlgili seçenekler: v2'de v1 ile aynı davranışın istenmesi durumunda json.OmitEmptyWithLegacyDefinition(true) seçeneği kullanılır.

3. string Etiket Seçeneğinin Davranış Değişikliği

  • v1 davranışı: Sayı, mantıksal ve dizgi türündeki alanlara uygulanır. İlgili değeri bir JSON dizgisi içinde yeniden kodlar (ör. int(42) -> "42"). Bileşik türlerin (dilim, harita vb.) içindeki değerlere özyinelemeli olarak uygulanmaz.
  • v2 davranışı: Yalnızca sayısal türlere uygulanır ve özyinelemeli olarak işler. Yani []int gibi bir dilimin içindeki sayılar da JSON dizgisi olarak kodlanır.
  • Değişikliğin nedeni: string seçeneğinin temel amacı, 64 bitlik tam sayıların hassasiyet kaybını önlemek için sayıları dizgi olarak temsil etmektir. v1'in davranışı sınırlı ve tutarsızdı. v2, bu temel kullanıma odaklanmış ve davranışı özyinelemeli olarak genişleterek daha kullanışlı hale getirmiştir.
  • İlgili seçenekler: v1'in davranışını taklit etmek için json.StringifyWithLegacySemantics(true) seçeneği kullanılabilir.

4. nil Dilim ve Harita Marshal İşlemi

  • v1 davranışı: nil dilimler ve nil haritalar null olarak marshal edilir.
  • v2 davranışı: Varsayılan olarak nil dilimler [] (boş dizi), nil haritalar ise {} (boş nesne) olarak marshal edilir.
  • Değişikliğin nedeni: nil, Go dilinin uygulama ayrıntısıdır ve dilden bağımsız bir JSON biçimine bunu yansıtmak uygun değildir. Boş koleksiyonları temsil eden [] ve {} daha yaygın bir gösterimdir.
  • İlgili seçenekler: v2'de json.FormatNilSliceAsNull(true) veya json.FormatNilMapAsNull(true) seçenekleriyle v1'deki gibi null olarak marshal edilebilir.

5. Dizi Unmarshal İşlemi

  • v1 davranışı: Go dizisine ([N]T) unmarshal ederken JSON dizisinin uzunluğu Go dizisinin uzunluğuyla farklı olsa da hata oluşturmaz. Uzunluk kısaysa kalan alanlar sıfır değerle doldurulur, uzunsa fazla kısım atılır.
  • v2 davranışı: JSON dizisinin uzunluğu Go dizisinin uzunluğuyla tam olarak eşleşmelidir. Aksi takdirde hata oluşur.
  • Değişikliğin nedeni: Go'da sabit boyutlu dizilerin uzunluğu çoğu zaman önemli bir anlam taşır. v1'in davranışı, verinin sessizce kaybolmasına yol açabilir. v2, daha katı kurallarla doğruluğu artırmıştır.
  • İlgili seçenekler: v1'in davranışını taklit etmek için json.UnmarshalArrayFromAnyLength(true) seçeneği kullanılabilir.

6. time.Duration İşleme

  • v1 davranışı: time.Duration, dahili olarak int64 gibi ele alınır ve nanosaniye cinsinden bir JSON sayısı olarak kodlanır.
  • v2 davranışı: time.Duration.String() metodu kullanılarak "1h2m3s" gibi biçimde bir JSON dizgisi olarak kodlanır.
  • Değişikliğin nedeni: Nanosaniye cinsinden sayılar okunabilirliği azaltır; time.Duration'ın standart dizgi gösterimi daha kullanışlıdır.
  • İlgili seçenekler: v1'in davranışını kullanmak için json:",format:nano" etiket seçeneği veya json.FormatTimeWithLegacySemantics(true) seçeneği kullanılabilir.

7. Geçersiz UTF-8 İşleme

  • v1 davranışı: Marshal/unmarshal sırasında bir dizgi içinde geçersiz UTF-8 baytları varsa bunları Unicode yedek karakteri (\uFFFD) ile sessizce değiştirir.
  • v2 davranışı: Varsayılan olarak geçersiz UTF-8 ile karşılaşıldığında hata döndürür.
  • Değişikliğin nedeni: Verinin sessizce bozulmasını önlemek ve daha katı JSON standardını (RFC 7493) izlemek amaçlanmıştır.
  • İlgili seçenekler: v1'in davranışını taklit etmek için jsontext.AllowInvalidUTF8(true) seçeneği kullanılabilir.

8. Yinelenen Nesne Üye Adlarının İşlenmesi

  • v1 davranışı: Bir JSON nesnesinde aynı adlı üyelerin yinelenmesine izin verir. Son görülen değer önceki değerin üzerine yazılır.
  • v2 davranışı: Varsayılan olarak yinelenen üye adı bulunduğunda hata döndürür.
  • Değişikliğin nedeni: RFC 8259 standardı yinelenen adların davranışını tanımlamadığından, uygulamalar farklı davranabilir. Bu durum bir güvenlik açığının kaynağı olabilir. v2, bunu açıkça reddederek doğruluğu ve güvenliği artırmıştır.
  • İlgili seçenekler: v1'in davranışını taklit etmek için jsontext.AllowDuplicateNames(true) seçeneği kullanılabilir.

Uygulama ve Mimari Farklılıklar

  • v1: decode.go içindeki decodeState ve scanner.go içindeki elle yazılmış durum makinesine (state machine) büyük ölçüde bağımlıdır. Bu, ayrıştırma mantığı ile anlamsal analizin sıkı biçimde birleşik olduğu tekparça (monolitik) bir yapıdır.
  • v2: Mimari daha modüler hale getirilmiştir.
    • encoding/json/jsontext: Düşük seviyeli, yüksek performanslı bir JSON belirteçleyicisi (tokenizer) (Decoder) ve kodlayıcısı (Encoder) sunar. Bu paket yalnızca JSON'un sözdizimsel (syntax) yönlerine odaklanır.
    • encoding/json/v2: jsontext üzerine inşa edilmiştir ve Go türleri ile JSON değerleri arasındaki anlamsal (semantic) dönüşümleri yönetir.
    • Bu ayrım sayesinde sözdizimsel ayrıştırma ile anlamsal analiz birbirinden ayrılmış, kodun açıklığı ve performansı artmıştır.

v2'nin Yeni API ve Özellikleri

v2, json.Options sistemi aracılığıyla çok esnek bir denetim imkânı sunar.

  • json.Options: Marshal/unmarshal davranışını değiştiren seçeneklerin kümesidir.
  • json.JoinOptions(...): Birden fazla seçeneği tek bir seçenekte birleştirir.
  • WithMarshalers / WithUnmarshalers: Marshaler/Unmarshaler arayüzlerini uygulamadan belirli bir tür için serileştirme/seri durumdan çıkarma mantığı eklemeye olanak tanıyan güçlü bir özelliktir. Bu, özellikle harici paketlerdeki türlerle çalışırken yararlıdır.
  • Yeni seçenekler: RejectUnknownMembers, Deterministic(false), FormatNilSliceAsNull gibi v1'de mümkün olmayan çeşitli davranış denetimleri artık kullanılabilir.

Sonuç

encoding/json v2, v1'deki deneyimlerden yola çıkarak doğruluk, performans ve esnekliği önemli ölçüde geliştiren çağdaş bir uygulamadır. Varsayılan davranışlar daha katı hale gelmiş olsa da, gelişmiş Options sistemi sayesinde v1'in tüm davranışları tam olarak desteklenir; böylece mevcut kodla uyumluluk korunarak v2'nin avantajları kademeli olarak benimsenebilir.

  • Yeni projeler için v2'nin varsayılan olarak kullanılması önerilir.
  • Mevcut projeler, jsonv1'i olduğu gibi kullanmaya devam edebilir veya jsonv2'ye geçiş yaparken DefaultOptionsV1() aracılığıyla v2'nin katı davranışlarını kademeli olarak benimseyen bir strateji izleyebilir.