Ana içeriğe geç

JSON'da Base64 Kullanımı: Binary Veri Taşıma

JSON'da Base64 Kullanımı: Binary Veri Taşıma - Veri ve API Rehberi

JSON bir metin formatıdır. Bu cümle teknik bir gözlem gibi görünür ama sonuçları çok daha derin: JSON yalnızca UTF-8 karakterlerini doğrudan taşıyabilir, binary veriyi taşıyamaz. Fotoğraf, PDF, ses dosyası veya şifreleme anahtarı gibi binary içerikleri JSON payload'una gömmek istediğinizde Base64 devreye girer. Ancak bu çözüm bedelsiz değildir.

JSON binary veriyi neden taşıyamaz

JSON spesifikasyonu (RFC 8259), yalnızca geçerli Unicode karakter dizilerini kabul eder. Binary verinin içerdiği bayt dizileri bu kısıtla doğrudan çelişir. Örneğin bir JPEG dosyasının başlık baytları <FF D8 FF> şeklinde başlar; bu değerler geçerli UTF-8 karakterleri değildir. JSON ayrıştırıcısı bu baytlarla karşılaştığında ya veriyi bozar ya da parse işlemini hatayla sonlandırır.

Null byte (0x00) özellikle sorunludur. Birçok dil ve kütüphane, string'i null byte'ta sonlandırıldığı varsayımıyla işler. Bu bayt JSON string'inin içinde yer aldığında davranış farklı kütüphanelerde farklılaşır: bazıları string'i keser, bazıları encoding hatası fırlatır, bazıları sessizce atlar. Sessiz atlama en tehlikeli olanıdır çünkü veri kaybı hata mesajı üretmeden gerçekleşir.

Kontrol karakterleri de problem üretir. ASCII 0-31 arası karakterlerin büyük bölümü JSON'da escape edilmeden kullanılamaz. Binary veri bu aralıktaki baytları sıklıkla içerdiğinden, ham binary'yi string olarak JSON'a yazmak yapısal bozulmaya yol açar.

Base64 kodlaması nasıl çalışır

Base64, binary veriyi 6-bitlik gruplara böler ve her grubu 64 karakterlik bir alfabeden seçilen yazdırılabilir bir karakterle eşleştirir. Standart alfabe A-Z, a-z, 0-9, + ve / karakterlerinden oluşur. Her 3 bayt giriş, 4 karakter çıktı üretir. Giriş boyutu 3'ün katı değilse sonuna = veya == padding karakteri eklenir.

Base64 bir şifreleme değil.

Bu ayrımı atlamak güvenlik açığına dönüşür; çünkü Base64 ile kodlanmış veri, standart bir kütüphane çağrısıyla saniyeler içinde orijinal haline döndürülebilir, bir şifreleme algoritmasının tersine çevrilmesi ise kriptografik anahtar gerektirir ve hesaplama açısından pratik değildir.

JavaScript'te iki yerleşik fonksiyon mevcuttur: btoa() ve atob(). btoa() binary string'i Base64'e, atob() ise Base64'ü binary string'e dönüştürür. Pratik bir örnek:

// Bir Uint8Array'i Base64'e dönüştürme
const bytes = new Uint8Array([72, 101, 108, 108, 111]);
const binaryStr = String.fromCharCode(...bytes);
const base64Str = btoa(binaryStr);
console.log(base64Str); // "SGVsbG8="

// JSON payload'u içine yerleştirme
const payload = JSON.stringify({
  filename: "dokuman.pdf",
  mimeType: "application/pdf",
  data: base64Str
});

Node.js ortamında Buffer sınıfı bu dönüşümü daha doğrudan sağlar:

// Node.js ile dosyayı okuyup Base64'e çevirme
const fs = require('fs');
const fileBuffer = fs.readFileSync('dosya.pdf');
const base64Data = fileBuffer.toString('base64');

// JSON'a göm
const payload = {
  filename: 'dosya.pdf',
  mimeType: 'application/pdf',
  data: base64Data
};

Alıcı tarafta decode işlemi için Buffer.from(base64Data, 'base64') yeterlidir. Base64 kodlama ve çözme işlemini tarayıcı üzerinde test etmek isteyenler için ayrı bir araç mevcuttur. JSON verisinin yapısını doğrulamak veya okunabilir formata almak için JSON doğrulama ve biçimlendirme aracını kullanabilirsiniz.

Payload boyutu ve transfer maliyeti

Base64 kodlaması her 3 baytlık girişi 4 karakter çıktıya dönüştürür. Bu matematiksel oranın pratik sonucu %33 boyut artışıdır. 100 KB'lık bir PNG dosyası, JSON payload'una Base64 olarak gömüldüğünde yaklaşık 133 KB'a çıkar.

Buna karşılık gzip sıkıştırması bu artışın bir bölümünü geri alabilir. Base64 çıktısının tekrar eden karakter örüntüleri gzip için elverişli bir yapı oluşturur. Pratikte gzip ile sıkıştırılmış Base64 veri, ham binary'nin gzip ile sıkıştırılmış boyutundan yalnızca %10-15 daha büyük olabilir. Bu fark tolere edilebilir düzeyde kalır.

Boyut endişesi asıl olarak büyük dosyalarda belirginleşir. 1 MB altındaki görseller, sertifikalar, küçük ses klipleri veya şifreleme anahtarları için Base64-in-JSON yaklaşımı çalışır. JSON'a gömmeden önce görselleri sıkıştırıp optimize etmek hem Base64 boyutunu hem de toplam payload'u küçültür. Dosya boyutu 1 MB'ı geçmeye başladığında Base64-in-JSON tercihini yeniden değerlendirin; alternatifleri ilerleyen bölümde ele alıyoruz.

Bellekte de maliyet vardır. JSON parse aşamasında tüm string belleğe alınır. 5 MB'lık Base64 verisi JSON parse edildiğinde heap'te anlık olarak birden fazla temsilcisi oluşabilir: ham string, decode edilmiş buffer ve uygulama değişkeni. Bellek kısıtlı ortamlarda bu yığılma anlamlı hale gelir.

Peki bu üç katman on eşzamanlı istekte ne anlam taşır?

Sık yapılan hatalar ve hata modları

Hata mesajı aldatıcı.

"Invalid character" veya "Unexpected token" hataları hem padding eksikliğinden hem de UTF-8/binary encoding çakışmasından kaynaklanabilir; iki hata türü bazen aynı belirti ürettiğinden, hatanın kaynağını doğru tespit etmek için önce padding karakterlerini (=) kontrol etmek, ardından girişin gerçekten geçerli Base64 alfabe karakterleri içerip içermediğini doğrulamak gerekir.

Padding eksikliği: Bazı sistemler padding karakterlerini siler veya URL parametrelerinde kaybolur. Base64 string'inin uzunluğu 4'ün katı olmalıdır. Değilse kalan karaktere göre = veya == eklenmesi gerekir. JavaScript'te bu düzeltme şu şekilde yapılabilir:

// Eksik padding'i tamamlama
function base64Fixup(str) {
  return str + '==='.slice((str.length + 3) % 4);
}

URL-safe alfabe karışıklığı: Standart Base64 + ve / karakterlerini kullanır. URL-safe Base64 ise bu karakterleri - ve _ ile değiştirir. JWT token'ları ve OAuth parametreleri genellikle URL-safe versiyonu kullanır. Bir sistemden gelen Base64'ü başka bir sistemde decode etmeye çalışırken alfabe uyuşmazlığı sessiz veri bozulmasına yol açar. Decode öncesinde alfabe tipini kesinleştirin.

btoa() ve multibyte karakter sorunu: btoa() yalnızca Latin-1 karakter aralığını kabul eder. Türkçe karakterler veya emoji içeren bir string'i doğrudan btoa()'ya vermek "The string contains characters outside of Latin1" hatasını fırlatır. Çözüm önce encodeURIComponent() ardından percent-encoding'i binary'ye çeviren bir ara adım eklemektir:

// Multibyte string için güvenli Base64 kodlama
function base64EncodeUnicode(str) {
  return btoa(
    encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (_, hex) =>
      String.fromCharCode(parseInt(hex, 16))
    )
  );
}

console.log(base64EncodeUnicode("Merhaba Dünya")); // Hata üretmez

Data URL öneki: <img src="data:image/png;base64,..."> sözdizimindeki data:image/png;base64, öneki Base64 verinin kendisi değildir. Bu öneki yanlışlıkla Base64 string'ine dahil ederek decode etmeye çalışmak her seferinde hata üretir. JSON'a yalnızca saf Base64 içeriği yazılmalı, MIME tipi ve önek ayrı alanlarda tutulmalıdır.

Base64 kullanmayı tercih ettiğiniz durumlarda JSON yapısını tutarlı tasarlamak sonraki bakım maliyetini düşürür. Saf Base64 içeriği, MIME tipi ve orijinal dosya adı her zaman ayrı alanlara yerleştirilmeli; decode eden taraf bu bilgilere bağımsız erişebilmelidir:

// Önerilen JSON yapısı
{
  "attachments": [
    {
      "filename": "rapor.pdf",
      "mimeType": "application/pdf",
      "encoding": "base64",
      "size": 45230,
      "data": "JVBERi0xLjQKJcOkw7..."
    }
  ]
}

// Kaçınılacak yapı: önek ve veri iç içe
{
  "file": "data:application/pdf;base64,JVBERi0xLjQKJcOkw7..."
}

"encoding" alanı şema versiyonları arasında esneklik sağlar; ileride binary protokole geçildiğinde yalnızca bu alan güncellenir, alıcı kod geri kalan yapıyı aynı şekilde işlemeye devam edebilir. "size" alanını bayt cinsinden eklemek de iyi bir pratiktir: alıcı taraf, decode öncesinde boyut sınırını kontrol edebilir ve beklenmedik büyük dosyaların bellek sorununa yol açmasını önceden önleyebilir. Veri alanı ile meta alanlarını birbirinden ayıran bu yapı, şema değişikliklerini geriye dönük uyumlu hale getirmenin en basit yoludur.

Ne zaman Base64 kullanmamak gerekir

Base64-in-JSON yaklaşımı her binary veri taşıma senaryosuna uymaz. Birkaç sınır durumu bu seçeneği verimsizleştirir.

Dosya boyutu 1 MB'ı geçtiğinde multipart/form-data tercih edilmeli. HTTP multipart formatı binary veriyi doğrudan taşır, Base64 overhead'i yoktur, sunucu taraflı streaming desteği mevcuttur. Büyük dosya yüklemelerinde Base64-in-JSON hem bellek kullanımını hem de işlem süresini gereksiz yere artırır.

Gerçek zamanlı veri akışı gerektiğinde de Base64-in-JSON uygunsuz kalır. WebSocket üzerinden video karesi veya ses verisi aktarırken binary WebSocket frame'leri kullanın. JSON parse maliyeti ve Base64 encode/decode döngüsü gecikmeye doğrudan katkıda bulunur.

Yüksek hacimli API çağrılarında Protocol Buffers veya MessagePack gibi binary protokoller değerlendirilmeli. Bu formatlar JSON'un okunabilirliğinden feragat eder ama payload boyutunu ve parse hızını belirgin şekilde iyileştirir. Saniyede yüzlerce çağrı yapılan servislerde bu fark ölçülebilir hale gelir.

Alternatif olarak dosyayı JSON'a gömmek yerine depolama servisine yükleyip URL'sini JSON'a yazmak yaygın bir yaklaşımdır. Bu yöntem hem payload'u küçük tutar hem de dosya servisinin önbellekleme ve CDN kabiliyetlerinden yararlanmanızı sağlar. Dosyanın tekrar tekrar gönderilmesi gerekmez; tek URL referansı yeterlidir.

JSON payload tasarımında Base64 bir araçtır, evrensel bir çözüm değildir. Küçük binary içerik, tek seferlik transfer veya mevcut JSON altyapısına entegrasyon gerektiğinde iyi çalışır. Boyut, performans veya streaming gereksinimlerinin öne çıktığı durumlarda ise başka protokollerle değiştirilmesi gereken bir geçici çözüm konumuna düşer.