Java API for JSON Processing – Stream bazlı JSON Üretmek ve Tüketmek
Java API for JSON Processing (JSON-P) standardı JSR-353 belirtiminde Java EE 7 şemsiyesi altında bulunan bir kurumsal java teknolojisidir. JSON-P ile JSON nesne ve dizilerini oluşturmak, oluşturulan JSON verilerini bir I/O ortamına okumak ve yazma işlemleri kolay bir biçimde gerçekleştirilebilmektedir. Bu yapılabilirliklerin konu alındığı yazıları okumak için devam eder bağlantıları izleyebilirsiniz (JSON-P-1 ve JSON-P-2).
Bu yazıda ise sizlerle JSON-P ile Stream bazlı JSON-P nesnelerinin nasıl üretilebileceğinden ve Stream bazlı veri sunabilen kaynaklardan, kısım kısım nasıl veri elde edilebileceğinden bahsetmek istiyorum. XML teknolojilerinde DOM ve SAX kütüphanelerini kullananlar, JSON-P içinde benzer yaklaşımların sunulup sunulmadığını merak edebilirler. Kısaca DOM ve SAX farkını özetleyecek olursak;
DOM
Java taraflı bir XML DOM ağacı oluşturulur ve tüm XML elamanları bir bütün olarak bellek alanında yer işgal ederler. DOM kullanımı SAX’a göre geliştirimi kolay iken, büyük XML veri yığınlarında bellek alanını tehdit eden sonuçlar doğurabilmektedir. JSON-P-1 ve JSON-P-2 uygulamalarında yer alan örnekler ve sınıflar, DOM kullanım yöntemine göre nesneleri bir bütün olarak kullanmaktadır.
SAX
Örneğin bir sunucu üzerinde çok uzun boyutlu bir XML kaynağı var, ve siz bu kaynak içerisindeki verilere bir bütün olarak değil de, belirli bir kısmına ihtiyaç duyuyorsunuz. Bu büyük boyutlu XML kaynağını tamamen uygulama içerisine çekmek mi istersiniz? Yoksa parça parça (stream) olarak mı elde etmek istersiniz? İşte SAX burada stream bazlı olarak verileri kısım kısım elde etmeyi sağlarken, bellek kullanımını verimli kılmaktadır. Fakat SAX ile veri elde ederken veya yazarken, DOM tekniğine göre geliştiriciye biraz daha fazla iş yükü bekler. Bu yazıda sunacağımız örnekler ve sınıflar ise, JSON-P’ nin sunduğu SAX (Simple API for XML) benzeri yaklaşımı açıklamaktadır.
Json-P standardında Stream bazlı operasyonlar için JsonGenerator ve JsonParser türünden nesneler kullanılmaktadır. JsonGenerator stream bazlı Json nesneleri oluşturmayı sağlarken, JsonParser ise verileri bir kaynaktan stream bazlı olarak kısım kısım okumaktadır.
JsonGenerator
JsonGenerator türünden nesneler Json#createGenerator metodu üzerinden oluşturulmaktadırlar. createGenerator(..) metodu Writer ve OutputStream ailelerinden sistem kaynaklarını metod parametresi olarak kabul etmektedir. Örneğin aşağıdaki örnekte üretilen JsonGenerator nesnesi, kendisi üzerinde yapılan işlemleri bir StringWriter üzerine aktarmaktadır.
try (JsonGenerator jsonUretec = Json
.createGenerator(stringWriter = new StringWriter())) {
jsonUretec.writeStartObject()
.write("ad", "Mahsun Doğan")
.write("yas", 25)
.writeEnd();
}
System.out.println(stringWriter.toString());
Bu kod parçasının çalıştırılmasının ardından aşağıdaki Json çıktısı elde edilir.
{"ad":"Mahsun Doğan","yas":25}
JsonGenerator nesneleri, StringWriter nesnesine veri yazdırdığı gibi FileWriter nesneleri üzerinden dosya sistemine de stream bazlı olarak kayıt yapabilir. Bunun için tek yapılması gereken bir File nesnesi ile birlikte bir FileWriter nesnesi oluşturmaktır.
File jsonFile=new File("./src/main/resources/file.json");
if (!jsonFile.exists()) {
jsonFile.createNewFile();
}
try (JsonGenerator jsonUretec2 = Json
.createGenerator(fileWriter = new FileWriter(jsonFile))) {
jsonUretec2.writeStartObject()
.write("ad", "Selçuk Bahadır")
.write("yas", 24)
.writeStartObject("adres")
.write("ulke", "Türkiye")
.write("il", "Hakkari")
.writeEnd()
.writeStartArray("hobiler")
.write("Yüzme").write("Atış").write("Futbol")
.writeEnd()
.writeEnd();
}
Buradaki JsonGenerator ile üretilen Json çıktısı ise, file.json dosyasına bakılarak görülür.
{ "ad":"Selçuk Bahadır", "yas":24, "adres":{ "ulke":"Türkiye", "il":"Hakkari" }, "hobiler":[ "Yüzme", "Atış", "Futbol" ] }
JsonParser
JsonParser Reader veya InputStream türlerinden bir kaynaktan verileri stream bazlı olarak okumaya ve çözümlemeye yaramaktadır. Örneğin http://maps.googleapis.com/maps/api/geocode/json?address=Marmara+University&sensor=false adresinden dönen Json nesnesi JsonParser ile çözümlenebilir ve elde edilen veriler ihtiyaca göre kullanılabilir. Stream bazlı yazma ve okuma işlemlerinin en önemli özelliği, işiniz bittiğinde veri/depo kaynağıyla iletişimi koparabilme yeteneğidir. Örneğin Json nesnesinin 10. satırındaki bir alanın değerine ihtiyacınız var ve 10. satırdan sonra var olan 100 satır bilgiyi okumak istemiyorsunuz. Bunun için JsonParser#close metodunu çağırarak aktif akışı durdurabilirsiniz. Bu sayede ihtiyaç duymadığınız verilerle sistemi meşgul etmek durumunda kalmamış oluruz.
JsonParser jsonParser = Json.createParser(new URL("http://maps.googleapis.com/maps/api/geocode/json?address=Marmara+University&sensor=false").openStream());
MultiMap multiMap = MultiValueMap.decorate(new HashMap());
while (jsonParser.hasNext()) {
JsonParser.Event next = jsonParser.next();
switch (next) {
case START_ARRAY:
case START_OBJECT:
case END_OBJECT:
case END_ARRAY:
break;
case KEY_NAME:
key = jsonParser.getString();
break;
case VALUE_STRING:
case VALUE_NUMBER:
case VALUE_TRUE:
case VALUE_FALSE:
case VALUE_NULL:
value = jsonParser.getString();
multiMap.put(key, value);
break;
}
}
System.out.println(multiMap);
JsonParser ile bir JSON verisi parse edilirken, her bir olay bildirimi JsonParser.Event ile elde edilir. Örneğin kaynakta “{“ ifadesiyle karşılaşılırsa, event tipi START_OBJECT, “}” ile karşılaşılırsa olay tipi END_OBJECT olmaktadır. Keza bir Json anahtarıyla karşılaşılırsa KEY_NAME, bu anahtara karşılık gelen değeri ise türüne göre VALUE_STRING, VALUE_NUMBER, VALUE_TRUE, VALUE_FALSE veya VALUE_NULL olarak elde edebilmekteyiz. Bu örnekte tüm anahtar -> değer çiftleri elde edilmekte ve bir MultiMap veri yapısına eklenerek çıktılanmaktadır. Uygulama çalıştırıldığında oluşan çıktı aşağıdaki gibidir.
{location_type=[APPROXIMATE], long_name=[Marmara University, Eğitim Mh., Kadıköy, Istanbul, Istanbul, Turkey, 34722], status=[OK], formatted_address=[Marmara University, Eğitim Mh., 34722 Kadıköy/Istanbul Province, Turkey], lng=[29.0552020, 29.0488970, 29.05504490, 29.0552020, 29.0488970], types=[establishment, neighborhood, political, sublocality, political, locality, political, administrative_area_level_1, political, country, political, postal_code, university, establishment], short_name=[Marmara University, Eğitim Mh., Kadıköy, Istanbul, Istanbul, TR, 34722], lat=[40.9898720, 40.9848090, 40.989210, 40.9898720, 40.9848090]}
Örnek uygulamalara https://github.com/rahmanusta/JSON-P/ adresinden erişebilirsiniz.
Tekrar görüşmek dileğiyle.