Bir API çağrısından sonra konsolda Unexpected token < in JSON at position 0 gördüğünüzde ilk refleks çoğu zaman aynıdır: JSON dosyası bozuldu. Oysa parser'ın verdiği bu mesaj her zaman eksik virgül ya da yanlış tırnak anlamına gelmez. Bazen sunucu JSON yerine HTML hata sayfası döndürür, bazen yanıtın başında görünmeyen bir BOM karakteri kalır, bazen de siz zaten parse edilmiş bir nesneyi ikinci kez JSON.parse() içine göndermiş olursunuz. Mesaj tek satırdır; fakat kök neden aynı değildir.
Bu hatanın zor tarafı, ilk bakışta çok genel görünmesidir. Parser yalnızca “burada beklemediğim bir karakter var” der ve durur. Hatanın nereden çıktığını sizin ayırmanız gerekir. Tek tırnak, trailing comma, yorum satırı, undefined, login sayfasına düşen bir istek ya da ters proxy arkasında dönen bambaşka gövde aynı semptomla karşınıza çıkabilir. Sorun JSON'un içinde olabilir, taşıma katmanında olabilir, hatta uygulama kodunun parse sırasındaki yanlış varsayımında olabilir.
İşin pratik yolu, ham gövdeyi önce doğrulama ekranında açıp parser'ın verdiği pozisyonu görmek ve ondan sonra yanıtın gerçekten JSON olup olmadığını ayırmaktır. Aynı panelde syntax doğrulama, beautify, schema kontrolü ve JSONPath gibi araçların bulunması da burada işe yarar; çünkü hata çözüldükten sonra yapısal doğrulamaya hemen geçebilirsiniz. Kritik olan nokta şu: Unexpected token bir sonuçtur, sebep değil.
Unexpected token mesajı tam olarak neyi anlatır?
JSON.parse() çalıştığında parser belirli sırada geçerli token'lar bekler. Nesne başlıyorsa {, dizi başlıyorsa [, string geliyorsa çift tırnak, sayı geliyorsa geçerli rakam dizisi görmeyi umar. Bunların yerine başka bir karakter gördüğünde “Unexpected token” hatası üretir. Yani mesaj, JSON gramerinin o pozisyonda izin vermediği bir karakterle karşılaşıldığını söyler.
Mesajdaki pozisyon bilgisi de önemlidir. Bu değer satır numarası değil, sıfır tabanlı karakter konumudur. Özellikle tek satırlık bir API yanıtında position 317 görmek sizi doğrudan doğru noktaya götürmeyebilir; yine de aramayı daraltır. Bu sitedeki JSON doğrulama aracı da hatayı kullanıcı dostu hale getirip pozisyonu ayrıca gösterir. Böylece kör arama yerine, parser'ın takıldığı bölgeyi hedef alırsınız.
| Hata örneği | İlk işaret | Çoğu zaman anlamı |
|---|---|---|
Unexpected token < at position 0 |
< |
JSON yerine HTML gövde dönüyor |
Unexpected token ' in JSON |
' |
Tek tırnak kullanılmış |
Unexpected token } in JSON |
} |
Trailing comma veya kapanış sırası bozuk |
Unexpected token u at position 0 |
u |
undefined benzeri metin parse ediliyor |
Unexpected token o at position 1 |
o |
Zaten nesne olan veri ikinci kez parse ediliyor |
Pozisyon bilgisi neden tek başına yeterli değildir?
Pozisyon yalnızca parser'ın tökezlediği yeri gösterir; hatanın gerçek başlangıcı bazen daha erkendedir. Örneğin bir nesnede sondaki virgül hatası varsa parser çoğu zaman kapanış parantezine geldiğinde şikayet eder. Hata karakteri } görünür ama asıl problem önceki satırdaki virgüldür. Aynı şekilde Unexpected token o mesajı da çoğu zaman karakterden çok veri tipine işaret eder; siz string beklerken nesne veriyorsunuzdur.
Bu yüzden pozisyonu ham log gibi okumak yerine bir ipucu olarak görmek gerekir. İlk kontrol karakterin kendisidir. İkinci kontrol, o karakterin gerçekten JSON'un parçası mı yoksa bambaşka bir yanıtın başlangıcı mı olduğudur. Büyük dosyalarda ilk 80-120 karakteri ayrıca görmek çoğu zaman tüm problemi birkaç saniyede açığa çıkarır.
En sık syntax kaynakları nelerdir?
İlk grup doğrudan JSON sözdiziminden çıkar. Bunlar nispeten kolay hatalardır; veri gerçekten JSON'dur ama kurala uymaz. Tek tırnak, tırnaksız anahtar, yorum satırı, trailing comma ve geçersiz literal'ler bu grubun başında gelir. JavaScript içinde geçerli görünen birçok yapı, saf JSON olarak geçerli değildir.
{'id': 7}
{"id": 7,}
{"id": 7 // yorum}
{id: 7}
{"oran": NaN}
Bu örneklerin tamamı JavaScript geliştiricisine tanıdık gelir. Sorun da bu noktada başlar. Geçerli JavaScript nesnesi ile geçerli JSON aynı şey değildir. JSON yalnızca altı veri tipi tanır ve anahtarları çift tırnakla ister. undefined, NaN, Infinity, fonksiyonlar ya da yorum satırları gramerin dışında kalır.
Satır sonları da sessizce problem çıkarabilir. String içinde kaçırılmamış yeni satır karakteri varsa parser yine beklenmeyen token hatası verir. Özellikle log mesajı, SQL sorgusu ya da kopyalanmış bir açıklama metni JSON içine elle yapıştırıldığında bu tür kırılmalar sık görülür. Kaynağı görmeden sadece kapanış tırnağına bakmak çoğu zaman yanıltır.
Manuel düzenlenen yapılandırma dosyalarında trailing comma en yaygın senaryolardan biridir. Yeni satır eklerken önceki satıra virgül koyulur, sonra son satır silinir ve kapanıştan önce fazladan virgül kalır. Parser kapanış köşeli ya da süslü paranteze geldiğinde hata verir. Yani görünen karakter değil, hemen öncesindeki artık virgül suçludur.
Kaçış karakterleri de ayrı dikkat ister. Windows dosya yolu, regex ya da çok satırlı metin kopyalarken \ karakteri sessizce anlam değiştirir. Örneğin "C:\new\logs" ifadesindeki \n yeni satır olarak yorumlanır; \l ise geçersiz escape ürettiği için parser hata verir. Bu tür metinler JSON içine girecekse ters bölülerin çift yazılması gerekir.
{
"dosyaYolu": "C:\\new\\logs\\app.log",
"regex": "^user\\/[0-9]+$"
}
Metni başka uygulamalardan yapıştırırken akıllı tırnaklar da sorun çıkarabilir. Görünüşte çift tırnak gibi duran tipografik işaretler, JSON parser için bambaşka karakterdir. Editör görsel olarak temiz dursa bile parser bunları string sınırı saymaz. Özellikle ofis dokümanlarından veya mesajlaşma araçlarından kopyalanan örnek JSON'larda bu ayrıntı şaşırtıcı derecede sık görülür.
Sorun her zaman JSON dosyasının içinde olmaz
Unexpected token hatalarının önemli kısmı, JSON beklenen yerde JSON gelmemesinden çıkar. En klasik örnek < karakteridir. Parser pozisyon 0'da < görüyorsa çoğu zaman gövde <!doctype html> ya da <html> ile başlıyordur. Yani API yanıtı yerine hata sayfası, login ekranı, 404 şablonu veya bakım ekranı dönüyordur.
Böyle durumlarda önce endpoint'in gerçekten doğru yere gittiğini kontrol etmek gerekir. Özellikle yanlış rewrite ya da yönlendirme kuralı yüzünden JSON uç noktası HTML sayfasına düşebilir. Eğer istek yolu beklenmedik biçimde başka route'a taşınıyorsa yönlendirme katmanını yeniden kurmak çoğu zaman parse hatasından daha önce çözülmesi gereken iştir.
Bir diğer kök neden BOM karakteridir. UTF-8 BOM görünmez; fakat yanıtın en başında kaldığında parser pozisyon 0 civarında anlamsız bir karakterle karşılaşır. Windows editörlerinden çıkan dosyalarda, PHP include zincirlerinde veya yanlış encode edilmiş export'larda bu durum yaşanır. Ham gövdeye bakmadan fark etmek zordur. Basit bir temizleme satırı yeterli olabilir:
const text = await response.text();
const clean = text.replace(/^\uFEFF/, "");
const data = JSON.parse(clean);
Unexpected token o at position 1 ise ayrı bir klasiği işaret eder. Bu hata genellikle string yerine nesne parse etmeye çalıştığınızda ortaya çıkar. Örneğin fetch tarafında await response.json() ile nesne elde etmişsinizdir; sonra aynı veriyi bir kez daha JSON.parse(data) ile işlersiniz. JavaScript nesnesi metne çevrilirken [object Object] oluşur ve parser ikinci karakterde o harfine çarpar.
Kimi projelerde gövde doğrudan JSON değil, önce çözülmesi gereken kodlanmış bir metin olabilir. Base64 gömülmüş bir payload ya da escape edilmiş ham string parse edilmeden önce açılmalıdır. Bu tür durumlarda gövdeyi önce çözümleyip temiz metni görmek daha doğru adımdır; aksi halde parser'a yanlış katmanda veri vermiş olursunuz.
HTTP 200 dönmesi de tek başına güvence değildir. Birçok sistem hata durumunda bile marka şablonunu 200 ile döndürür; siz yalnızca içeriğin HTML olduğunu sonradan fark edersiniz. Aynı şekilde güvenlik duvarı, bakım katmanı veya oturum zaman aşımı sayfası da başarılı yanıt gibi görünebilir. Bu yüzden parse hatasını yalnızca status koduyla açıklamaya çalışmak eksik kalır. İlk karakter, içerik tipi, nihai URL ve yanıt gövdesinin ilk satırları birlikte okunmalıdır.
Burada küçük ama önemli bir ayrım daha vardır: boş gövde parse edilirse çoğu zaman Unexpected end of JSON input görürsünüz, Unexpected token değil. 204 No Content yanıtlarını, silme işlemlerini veya boş fallback gövdeleri parse etmeye kalkarsanız problem token değil veri yokluğudur. Mesaj türünü doğru ayırmak, sizi yanlış katmanda saatlerce arama yapmaktan kurtarır.
Parse hatasını adım adım nasıl izole edersiniz?
En sağlam yöntem, response.json() ile doğrudan atlamak yerine ilk denemede ham gövdeyi almaktır. Çünkü parser hata verdiğinde çoğu framework gövdenin kendisini kaybettirir, elinizde yalnızca exception mesajı kalır. Oysa ilk 100 karakteri görmek çoğu kez tüm resmi açar.
- Yanıtı önce metin olarak alın ve ilk 100-150 karakteri görün.
- HTTP status ve
content-typebaşlığını ayrıca kontrol edin. - Ham gövdeyi doğrulama aracına yapıştırıp pozisyon bilgisini okuyun.
- Hata pozisyonunun öncesindeki 20-30 karakteri özellikle inceleyin.
- Syntax düzeldikten sonra şema ve alan yapısını ayrı kontrol edin.
const response = await fetch(url);
const text = await response.text();
const contentType = response.headers.get("content-type") || "";
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${text.slice(0, 120)}`);
}
if (!contentType.includes("application/json")) {
throw new Error(`JSON yerine ${contentType} döndü`);
}
const data = JSON.parse(text.replace(/^\uFEFF/, ""));
Bu akışın faydası, transport sorunu ile syntax sorununu aynı yere yıkmamasıdır. HTTP 302, 401 ya da 500 dönüyorsa bunu parse problemi diye okumazsınız. Başlık doğru ama gövde bozuksa orada syntax seviyesine inersiniz. Tek satırlık büyük yanıtlar için pozisyon etrafındaki parçayı kesip incelemek de önemlidir; bazen tüm dosyayı gözle taramak yerine 30 karakterlik çevre size yeter.
Ham gövde geçerli JSON çıktığında iş henüz bitmez. Aynı araçta beautify ile okunabilir hale getirmek, sonra JSONPath ile ilgili alanı ayıklamak ve gerekiyorsa diff görünümünde beklenen yanıtla kıyaslamak çok zaman kazandırır. Parse aşaması yalnızca kapıyı açar; veri sözleşmesinin doğru olup olmadığı bir sonraki katmandır.
Tarayıcı geliştirici araçlarının Network paneli bu süreçte düşündüğünüzden daha değerlidir. Preview sekmesi bazen parse edilmiş görünüm sunduğu için sorunu saklar; gerçek teşhis için Response sekmesine, hatta mümkünse ham metne bakmak gerekir. Eğer yanıtın başında <!doctype html>, Warning:, BOM izi ya da beklenmedik login işaretleri görüyorsanız, sorun JSON gramerinden önce başlamıştır. Parser yalnızca bu üst katman bozulmasının ilk alarmını vermiştir.
Uzun payload'larda karakter pozisyonunu satıra çevirmek için küçük yardımcı yöntemler de işe yarar. Ham gövdeyi geçici olarak bir editöre yapıştırıp belirtilen pozisyona gidin, oradan 20 karakter geri ve 20 karakter ileri bakın. Hata çoğu zaman tam o noktada değil, hemen öncesindeki tırnak veya virgüldedir. Pozisyonu satır numarasına çevirmeye uğraşmadan çevresel okuma yapmak daha hızlıdır.
Parse hatası ile şema hatasını karıştırmamak gerekir
Geçerli JSON, doğru JSON demek değildir. Şu gövde parse edilir:
{
"users": "Ali, Ayşe, Mehmet",
"total": "3"
}
Parser buna itiraz etmez; çünkü sözdizimi doğru. Fakat uygulamanız users alanında dizi, total alanında sayı bekliyorsa iş mantığı daha sonra kırılır. Bu nedenle parse düzeltildikten sonra şema doğrulaması ayrı yapılmalıdır. Aynı paneldeki schema validator bu farkı ortaya koyar: syntax temizdir ama veri sözleşmesi yanlıştır.
Bu ayrımı net kurmak ekip içinde ciddi zaman kazandırır. Aksi halde backend ekibi “JSON geçerli” der, frontend ekibi “uygulama bozuluyor” der ve herkes farklı problemi tartışır. Parse sorunu, veri yapısı sorunu ve iş kuralı sorunu aynı başlık altında konuşulmaya başladığında teşhis yavaşlar. İyi hata ayıklama, katmanları birbirinden ayıran hata ayıklamadır.
Özellikle OpenAPI, schema tabanlı doğrulama ya da typed client kullanan projelerde parse hatası ilk engeldir; fakat son kontrol değildir. Şema doğrulamasını CI hattına almak, beklenen alanları sözleşme olarak saklamak ve örnek yanıtları diff ile karşılaştırmak üretimde yaşanacak sürprizleri belirgin biçimde azaltır.
Üretimde bu hatayı azaltmak için hangi önlemler işe yarar?
İlk savunma hattı, JSON üretimini string birleştirerek değil güvenilir serializer ile yapmaktır. Backend tarafında json_encode, JSON.stringify ya da dilin standart serileştirme araçları kullanılmalıdır. Elle tırnak ekleyerek, yorum saklayarak ya da virgül yöneterek JSON üretmek küçük projede bile sürdürülebilir değildir.
İkinci katman, çıkışın etrafına sızan yan etkileri kesmektir. PHP warning'leri, debug echo'ları, BOM'lu include dosyaları ve login yönlendirmeleri çoğu zaman parse hatası olarak görünür. Uygulama JSON dönüyorsa gerçekten yalnızca JSON dönmelidir. Üretimde hata log'u ayrı kanala gitmeli, yanıt gövdesine karışmamalıdır.
Üçüncü katman, istemci tarafında tek bir güvenli parse yordamı kullanmaktır. Her ekip üyesinin her yerde farklı try/catch yazması yerine merkezi yardımcı fonksiyon kullanmak daha güvenlidir. İçinde status, content-type, ilk 120 karakter log'u ve BOM temizliği bulunan tek bir yardımcı, dağınık parse alışkanlığından daha az sürpriz üretir.
El ile düzenlenen yapılandırma dosyaları için pre-commit kontrolü de güçlü bir fren olur. package.json, deployment manifest'i ya da yerel mock yanıtları commit öncesi doğrulanıyorsa tek tırnak ve trailing comma gibi sorunlar repoya hiç girmez. Özellikle çok geliştiricili ekiplerde bu tür küçük otomasyonlar, üretimde görülen büyük parse hatalarının önünü keser.
Log stratejisi de burada dengeli kurulmalıdır. Parse hatasında tüm gövdeyi üretim log'una basmak hızlı görünür ama içinde token, e-posta, kişisel veri veya oturum bilgisi varsa yeni bir risk üretirsiniz. Daha savunulabilir yaklaşım, status kodu, content-type, ilk 120 karakter, response URL ve karakter pozisyonunu loglamaktır. Teşhis için yeterli bilgi kalır, veri sızıntısı yüzeyi büyümez.
Son katman, sözleşme testidir. Backend belirli bir route için hangi alanları ve tipleri döndürüyor, frontend ne bekliyor, ara katmanlar hangi header'ı koruyor? Bunlar CI içinde düzenli test edildiğinde Unexpected token hatası çoğu zaman kullanıcıya ulaşmadan yakalanır. Manuel kontrol yerine otomatik sözleşme testi kuran ekipler parse sorununu hata ayıklama işi olmaktan çıkarıp teslimat kalitesi problemine dönüştürür.
Unexpected token hatasını çözmenin pratik yolu, önce ham gövdeyi görmek ve problemi syntax, transport ve şema katmanlarına ayırmaktır. Status kodu, content-type, ilk karakterler ve pozisyon bilgisi birlikte okunduğunda bu hata panik sebebi olmaktan çıkar; hızlı teşhis edilen ve tekrarını önleyebildiğiniz bir sözleşme ihlaline dönüşür.