Bir proxy şirketinin bunu neden yazdığına dair kısa bir not: Biz Geonode’uz ve müşterilerimizin bildirdiği en yaygın JSON hatası, aldıkları verilerde “Unexpected token '<'” hatasıdır. Bu, yanıtın HTML olduğu anlamına gelir — bir hata sayfası, oturum açma yönlendirmesi veya engelleme sayfası — ve ayrıştırıcı, JSON verilmediğini doğru bir şekilde bildiriyor. Herhangi bir kod değişikliği yapmadan önce, aldığınız verinin ilk 200 karakterini günlüğe kaydedin. Bu makaledeki diğer hemen hemen tüm açıklamalar, dosyanın gerçekten JSON olduğu varsayımına dayanmaktadır ve bu varsayım, en sık hataya yol açan unsurdur.
Node'da: Diskten Okuma
Üç farklı yaklaşım var ve bunlardan biri modern çözümdür.
**fs/promises
, standart yol:**
import { readFile } from "node:fs/promises";
const raw = await readFile("./data.json", "utf8");
const data = JSON.parse(raw);
Node belgeleri encoding argümanı konusunda nettir ve bu önemlidir: encoding belirtilmezse, readFile
"dosya içeriğini içeren bir <Buffer>
nesnesiyle yerine getirilen bir promise döndürür"; encoding belirtilirse, "<string>
ile yerine getirilir". JSON.parse
, bir Buffer'ı string'e dönüştürerek kabul eder; bu nedenle kodlamayı atlamak genellikle işe yarar ve gereksiz yere fazladan dönüştürme işlemi yapılır. "utf8"
kullanın.
Ayrıca, signal
seçeneği aracılığıyla AbortSignal
'i de destekler; bu, "devam eden bir readFile
işlemini iptal etmenize olanak tanır" — okuma işlemi, iptal edilebilecek bir isteğin parçası olduğunda kullanışlıdır.
Başlangıç kodu için senkron:
import { readFileSync } from "node:fs";
const config = JSON.parse(readFileSync("./config.json", "utf8"));
Sunucunuz hizmet vermeye başlamadan önce engelleme sorun değildir. Ancak, her bağlantı için olay döngüsünü durduran istek işleyicisi içinde bu durum sorun yaratır. Bu ayrım, kuralın tamamını oluşturur.
**require
, yalnızca CommonJS’de:**
const data = require("./data.json");
Kısa ve öz olup, insanların genellikle unuttuğu iki özelliğe sahiptir. Önbelleğe alır; bu nedenle, aynı yoldaki ikinci bir require
çağrısı, dosyayı yeniden okumadan aynı nesneyi döndürür — bu da, dosyayı çalışma zamanında düzenlemenin hiçbir etkisi olmadığı anlamına gelir. Ayrıca ES modüllerinde kullanılamaz.
Öznitelikleri İçe Aktarma: Standart Yöntem
Çoğu kişinin henüz benimsemediği ve yeni kodlarda kullanılması gereken sözdizimi.
import data from "./data.json" with { type: "json" };
Veya dinamik olarak:
const data = await import("./data.json", { with: { type: "json" } });
MDN, bunu Baseline 2025 olarak kaydeder; bu özellik, Nisan 2025'ten itibaren en yeni tarayıcılarda kullanılabilir hale gelir; Node ve Deno gibi tarayıcı dışı çalışma ortamları da JSON modülleri için tarayıcı semantiğine uyum sağlar.
type: "json"
özniteliği sadece süs amaçlı değildir. MDN, bunun "bir modülün application/json
MIME türüyle sunulduğunu doğruladığını" ve dosyanın "application/json
dışında herhangi bir medya türüyle sunulması durumunda içe aktarma işleminin başarısız olacağını" açıklamaktadır.
Güvenlikle ilgili gerekçe, bu özniteliğin neden isteğe bağlı değil de zorunlu olduğunu açıkladığı için alıntı yapmaya değer:
Herhangi bir nedenle (örneğin, sunucunun ele geçirilmesi veya sahte olması), sunucu yanıtındaki medya türü
text/javascript
(JavaScript kaynağı için) olarak ayarlanırsa, dosya ayrıştırılır ve kod olarak çalıştırılır. "JSON" dosyası gerçekten kötü amaçlı kod içeriyorsa, import
bildirimi istemeden harici kodu çalıştırarak ciddi bir tehdit oluşturur.
Geçişle ilgili bir not: Daha önceki bir öneride, with
yerine assert
anahtar sözcüğü kullanılmıştı. MDN bunu uyumsuz bir değişiklik olarak işaretlemektedir — assert
kullanan uygulamalar "artık desteklenmemektedir". Eski kodlarda veya bir öğreticide assert { type: "json" }
ifadesini görürseniz, bunun güncellenmesi gerekir.
Tarayıcıda: JSON Alımı
En yaygın durum ve bir tuzak barındıran durum.
const res = await fetch("/data.json");
if (!res.ok) throw new Error(`HTTP ${res.status} from ${res.url}`);
const data = await res.json();
res.ok kontrolü isteğe bağlı değildir ve bunu atlamak, bu makalenin giriş bölümünde bahsedilen hatanın doğrudan nedenidir. fetch, HTTP hata durumlarını reddetmez — 403, 404 ve 500 hataları normal şekilde çözümlenir. .json() çağrısı, bir hata sayfasını ayrıştırmaya çalışır ve JSON'unuzla hiçbir ilgisi olmayan < karakteriyle ilgili bir sözdizimi hatası alırsınız.
Yararlı bir şekilde hata veren bir sürüm için:
async function fetchJson(url) {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status} from ${url}`);
const type = res.headers.get("content-type") ?? "";
if (!type.includes("application/json")) {
const body = await res.text();
throw new Error(`Expected JSON, got ${type}: ${body.slice(0, 200)}`);
}
return res.json();
}
İki satırlık kontrol, anlaşılmaz bir ayrıştırma hatasını, durumu, içerik türünü ve gerçekte neyin geldiğini belirten bir mesaja dönüştürür.
Ayrıca, Response.json()'ın bir reviver işlevi kabul etmediğini de unutmayın. Eğer bir reviver işlevine ihtiyacınız varsa — tarih dönüştürme veya büyük tamsayıları işleme için — res.text()'ı ve ardından JSON.parse'ı kullanın. Reviver'lar ve hassasiyet sorununu JSON.parse kılavuzumuzda ele aldık.
Tarayıcıda: Kullanıcının Seçtiği Bir Dosya
Kullanıcının bilgisayarından seçilen bir dosya için File API’sini kullanın.
<input type="file" id="picker" accept="application/json">
document.getElementById("picker").addEventListener("change", async e => {
const file = e.target.files[0];
if (!file) return;
try {
const data = JSON.parse(await file.text());
console.log(data);
} catch (err) {
console.error(`Could not parse ${file.name}: ${err.message}`);
}
});
File.text()
, içeriği bir dize olarak döndüren bir promise verir; bu, olay işleyicileri içeren eski FileReader
yöntemine kıyasla çok daha derli topludur. Çok büyük bir dosyada ilerleme olaylarını izlemek istiyorsanız, FileReader
hâlâ ihtiyacınız olan yöntemdir.
Unutulmaması gereken iki nokta vardır. Tarayıcılar rastgele yerel yolları okuyamaz — kullanıcı dosyayı seçmelidir ve bu, aşılmaya çalışılması gereken bir sınırlama değil, kasıtlı olarak konulmuş bir güvenlik sınırlamasıdır. Ayrıca, .json
uzantısı içerik hakkında hiçbir garanti vermez; dolayısıyla asıl işi try
/catch
yapmaktadır.
Sürükle ve bırak işlevi, event.dataTransfer.files
adresinden alınan aynı File
nesnelerini kullanır.
Size Bir Şey Anlatan Hata İşleme
Beş dakikalık bir sorunu bir saatlik bir sorundan ayıran alışkanlık.
function parseJson(text, source) {
try {
return JSON.parse(text);
} catch (err) {
throw new Error(
`Failed to parse JSON from ${source}: ${err.message}. ` +
`First 200 chars: ${text.slice(0, 200)}`
);
}
}
Önemli olan kesittir. JSON.parse hataları bir konumu ve bir karakteri belirtir; asıl girdi ise size nedenini söyler. Çoğu durumu üç imza kapsar:
Unexpected token '<' — içerik HTML'dir. Bir hata sayfası, bir oturum açma yönlendirmesi veya bir dizin listesi.
Unexpected end of JSON input — girdi boştur veya kesilmiştir. Bir 204 yanıtı, tam olarak yazılmayan bir dosya veya beklemeyi unuttuğunuz bir get işlemi.
Unexpected token '}' makul bir konumda — gerçekten hatalı biçimlendirilmiş JSON; genellikle sondaki virgül. JavaScript bunlara izin verse de JSON bunları yasaklar.
Dosya okumalarında, okuma hatasını ayrıştırma hatasından ayırt edin. ENOENT, dosyanın mevcut olmadığı anlamına gelir; bu, geçersiz içeriklerden farklı bir sorundur ve farklı bir mesaj gerektirir.
Okuduklarınızı Doğrulama
Ayrıştırma işlemi başarıyla tamamlandı. Bu, sözdiziminin geçerli olduğunu gösterir; ancak verilerin kodunuzun beklediği biçime sahip olup olmadığına dair hiçbir bilgi vermez — ve bu iki durum arasındaki fark, üretim ortamındaki arızaların şaşırtıcı derecede büyük bir kısmının kaynağıdır.
Ayrıştırma ve doğrulama ayrı adımlardır. JSON.parse
, anahtarda yazım hatası olan {"user": {"nmae": "Ada"}}
adresini veya sayı beklenirken "n/a"
dizesini içeren price
alanını sorunsuzca döndürür. Kodunuz daha sonra, asıl sorundan birkaç işlev uzaklıkta, bir yerde hata verir ve bu hata, nedeni değil, sadece bir belirtiyi gösterir.
Kontrolünüz dışındaki her şey için, bir şemaya göre doğrulama yapın. Birçok kütüphane bunu iyi bir şekilde halleder ve hangisini seçerseniz seçin, izlenen yol aynıdır:
const Config = z.object({
port: z.number().int().min(1).max(65535),
host: z.string(),
retries: z.number().int().default(3),
features: z.array(z.string()).optional(),
});
const config = Config.parse(JSON.parse(await readFile("./config.json", "utf8")));
Artık hata mesajında alanın adı, beklenen tür ve bulunan değer belirtilir — bu da sorunu beş dakikada çözmekle bir öğleden sonra harcamak arasındaki farkı belirler.
Basit durumlarda, birkaç doğrulama hiçbir maliyeti yoktur:
const data = JSON.parse(raw);
if (!Array.isArray(data.items)) throw new Error("items must be an array");
if (data.items.length === 0) throw new Error("items is empty — check the source");
Bu ikinci kontrol, göründüğünden daha değerlidir. Boş bir dizi geçerli bir JSON'dur, sorunsuz bir şekilde ayrıştırılır ve çoğu zaman meşru bir sonuçtan ziyade bir belirtidir — örneğin, bir filtre hatalı olduğu için hiçbir şey döndürmeyen bir API veya değişmiş bir sayfadan başarılı bir şekilde veri toplayan bir kazıma işlemi.
Kendiniz üretmediğiniz sayılara şüpheyle yaklaşın. JSON sayıları JavaScript double türüne dönüştürülür; bu nedenle, Number.MAX_SAFE_INTEGER
değerinden büyük tamsayılar, hiçbir aşamada hata vermeden sessizce hassasiyet kaybeder. Tanımlayıcılar genellikle bu durumdan zarar görür: iki farklı veritabanı kaydı aynı değere ayrıştırılabilir. Bir alan miktar yerine bir tanımlayıcıysa, JSON'da bir dize olmalıdır — ve üreticiyi kontrol etmiyorsanız, reviver'ın context.source
argümanı size orijinal rakamları verir.
Ve sınırda, tek seferde doğrulayın. Verilerin programınıza girdiği noktada yapısını kontrol etmek, sonraki tüm aşamaların verilerin doğru olduğunu varsayabileceği anlamına gelir. Yirmi farklı yerde önleyici kontrol yapmak, yirmi farklı yeri güncellemek anlamına gelir ve sözleşmenin yazılı olduğu tek bir nokta kalmaz.
Büyük Dosyalar
JSON.parse
işlemi eşzamanlıdır ve belgenin tamamının bellekte bulunmasını gerektirir. Dosyalar büyüdükçe her iki durum da sorun haline gelir.
Genel bir kılavuz. Bir megabaytın altındaysa, bu konuyu dert etmeyin. Bir ile on arasında ise ölçüm yapın — özellikle tarayıcıdaki ana iş parçacığında; burada ayrıştırma işlemi görüntülemeyi engeller ve gözle görülür takılmalara neden olur. On megabaytın üzerinde veya kullanılabilir belleğinizin yaklaşık onda birinin üzerinde ise, başka bir yol izleyin.
İşlemi ana iş parçacığından başka bir yere taşıyın. Tarayıcıda, bir Web Worker arayüzü dondurmadan ayrıştırma işlemini gerçekleştirir. Node'da ise bir iş parçacığı, olay döngüsü için aynı işlevi görür.
Satır sonu ile sınırlandırılmış JSON kullanın. Bu, geçici bir çözümden ziyade yapısal bir düzeltmedir. Her satıra bir JSON belgesi düşmesi, herhangi bir boyuttaki dosyayı sabit bellek alanında işleyebileceğiniz, her satırın bağımsız olarak ayrıştırılacağı ve kesilmiş bir dosyanın bile tüm kayıtları eksiksiz olarak vereceği anlamına gelir:
import { createReadStream } from "node:fs";
import { createInterface } from "node:readline";
const rl = createInterface({ input: createReadStream("./data.jsonl") });
for await (const line of rl) {
if (line.trim()) handle(JSON.parse(line));
}
Biçimi siz kontrol ediyorsanız, bu, zamanla eklenecek her şey için daha iyi bir tasarımdır — ve çöken bir toplama işinin, ayrıştırılamaz bir dosya yerine kullanılabilir bir dosya bırakmasının nedeni de budur.
Biçim, değiştiremeyeceğiniz tek bir büyük dizi ise bir akış ayrıştırıcısı kullanın. Birçok kütüphane, tüm ağacı oluşturmak yerine değerleri geldikçe döndürür.
Ya da daha azını isteyin. Sayfalandırma, alan seçimi, daha dar bir uç nokta. Neredeyse her zaman doğru cevap budur ve neredeyse her zaman göz ardı edilir, çünkü API’nin sahibiyle görüşmeyi gerektirir.
JSON'u Geri Yazma
Kısaca, genellikle bir sonraki soru bu olduğu için bunun tam tersi.
import { writeFile } from "node:fs/promises";
await writeFile("./out.json", JSON.stringify(data, null, 2), "utf8");
null, 2
argümanları girintili bir çıktı üretir; bu, göründüğünden daha önemlidir: Bir insan tarafından okunacak veya sürüm kontrolünde karşılaştırılacak bir dosya biçimlendirilmelidir, ancak aktarılacak bir dosya biçimlendirilmemelidir.
JSON.stringify
komutunda kaybolan ve hata yerine sessiz veri kaybına neden olan üç şey vardır. undefined
değerleri ve işlevleri nesnelerden tamamen çıkarılır ve diziler içinde null
haline gelir. Date
nesneleri ISO dizelerine dönüşür, bu nedenle bir reviver olmadan tarihlere geri dönüştürülemezler. Ve BigInt
doğrudan hata verir — standart yaklaşım, büyük tamsayıları dize olarak serileştirmektir.
Ekleme yapmak için, diziyi yeniden yazmak yerine satır sonu ile ayrılmış JSON yazın:
import { appendFile } from "node:fs/promises";
await appendFile("./log.jsonl", JSON.stringify(record) + "\n", "utf8");
Bir JSON dizisine ekleme yapmak, diziyi okumayı, ayrıştırmayı, ekleme işlemini gerçekleştirmeyi ve tüm dosyayı yeniden yazmayı gerektirir — bu işlem maliyetlidir ve yazma işlemi yarıda kesilirse dosyayı bozar.
Kullanıcılar Ayrıca Şunları Soruyor
Node.js'de bir JSON dosyasını nasıl okurum?
const data = JSON.parse(await readFile("./data.json", "utf8")) adresindeki node:fs/promises komutunu kullanarak. Ya da standart içe aktarma sözdizimini kullanabilirsiniz: import data from "./data.json" with { type: "json" }; bu, 2025 itibarıyla Baseline standardıdır ve hem Node'da hem de tarayıcılarda çalışır.
JavaScript, tarayıcıda yerel bir dosyayı okuyabilir mi?
Yol bilgisiyle değil. Tarayıcılar, kasıtlı bir güvenlik sınırlaması nedeniyle rastgele yerel dosyaları açamaz. Kullanıcı, <input type="file"> aracılığıyla veya sürükle-bırak yöntemiyle bir dosya seçmelidir; ardından File.text() komutu size dosyanın içeriğini verir.
import ... with { type: "json" } ne işe yarar?
Bir JSON dosyasını modül olarak içe aktarırken, sunucunun dosyayı application/json MIME türüyle sunduğunu doğrular. Bu kontrol yapılmazsa, text/javascript olarak sunulan bir dosya kod olarak ayrıştırılıp çalıştırılır — bu özniteliğin önlemek için var olduğu güvenlik sorunu da budur.
JSON okurken neden "Beklenmeyen belirteç '<'" hatası alıyorum?
Çünkü içerik < ile başlıyor; bu, JSON yerine HTML aldığınız anlamına gelir — genellikle bir hata sayfası veya oturum açma yönlendirmesi. fetch durumunda ise, fetch HTTP hata durumlarında reddetme yapmadığından, neden neredeyse her zaman eksik bir res.ok kontrolüdür.
JSON için require mi yoksa import mı kullanmalıyım?
Yeni kodlarda import ... with { type: "json" } kullanın; çünkü bu standarttır ve ES modüllerinde çalışır. require yalnızca CommonJS'ye özeldir ve sonucu önbelleğe alır; bu nedenle çalışma zamanında düzenlenen bir dosya yeniden okunmaz. Programınız çalışırken değişen dosyalar için ikisi de uygun değildir — bu tür dosyalar için readFile kullanın.
Çok büyük bir JSON dosyasını nasıl okurum?
Bir işçi (worker) kullanarak ayrıştırma işlemini ana iş parçacığından ayırın veya verileri satır sonu ile ayrılmış JSON olarak yeniden yapılandırın; böylece her satır sabit bellekte bağımsız olarak ayrıştırılır. Değiştirilemeyen büyük bir dizi için akış tabanlı bir ayrıştırıcı kullanın. Ayrıca, en başından daha az veri talep edip edemeyeceğinizi de değerlendirin.
JSON.parse ile response.json() arasındaki fark nedir?
Response.json(), bir getiş gövdesini okur ve tek adımda ayrıştırır; ayrıca bir reviver işlevi kabul etmez. JSON.parse ise elinizde bulunan bir dize üzerinde çalışır ve reviver işlevi kabul eder. Tarihler veya büyük tamsayılar için bir reviver'a ihtiyacınız varsa, res.text() komutunu kullanın ve ardından JSON.parse komutunu uygulayın.
Var olmayabilecek bir JSON dosyasını nasıl işlerim?
Okuma hatasını ayrıştırma hatasından ayrı olarak yakalayın. Node'da, ENOENT hata kodu dosyanın eksik olduğu anlamına gelir; bu durum genellikle bir hata yerine varsayılan bir değer gerektirir — oysa SyntaxError kodu, dosyanın mevcut olduğu ancak içeriğinin yanlış olduğu anlamına gelir.
Sonuç
Yöntem, ortama bağlıdır ve günümüzde bu konuda daha tekdüze bir yaklaşım benimsenmektedir. import data from "./data.json" with { type: "json" }, Node’da ve tarayıcılarda çalışır; 2025 itibarıyla Baseline statüsündedir ve sadece formalite olarak değil, gerçek bir güvenlik nedeni nedeniyle uygulanan bir MIME türü kontrolü içerir.
Programınız çalışırken değişen dosyalar için, bunları açıkça okuyun — Node'da "utf8" kodlamasıyla readFile, tarayıcıda res.ok kontrolüyle fetch ve kullanıcının seçtiği bir şey için File.text(). Bu res.ok kontrolü, bu makaledeki en değerli satırdır; çünkü bunu atlamak, en yaygın JSON hatasının doğrudan nedenidir.
Bir hata oluştuğunda, herhangi bir koda dokunmadan önce girdinin ilk 200 karakterini günlüğe kaydedin. Unexpected token '<', HTML anlamına gelir; Unexpected end of JSON input ise boş veya kesik anlamına gelir; her ikisi de, ayrıştırıcı hakkında varsayımlarda bulunmak yerine, gerçekte ne geldiğine bakılarak yanıtlanır.
Dosyalar büyüyorsa, yapısal çözüm daha büyük bir makine kullanmak yerine satır sonu ile sınırlandırılmış JSON'dur. Her satıra bir belge yerleştirildiğinde, bellek kullanımı sabit kalır, güvenli bir şekilde ekleme yapılır ve kesintiye uğrayan bir yazma işlemi durumunda tüm kayıtlar bozulmadan korunur.
