Erhan Yakut Software Developer @Binalyze | Founder @Passwall | Golang Enthusiast | Open Sorcerer

Go ile Büyük JSON Dosyalarını Okuma

3 min read

Her geçen gün tüm altyapısını Go’ya geçiren yeni bir firma duyuyoruz. Bunun sebebi de tahmin edersiniz ki Go’nun sunmuş olduğu kolaylıklar. İşte bunlardan birisi de JSON Data Streaming. Bu özellik hangi problemi çözüyor derseniz başlıkta da belirttiğim gibi büyük boyutlu JSON dosyalarınızı dertsiz tasasız okumaya yarıyor.

Problem Tanımı

Go ile normal bir şekilde JSON dosyası okumak gerçekten kolaydır. Elinizde bir JSON verisi bulunur. Buna karşılık gelen struct’ı (model) oluşturursunuz. Ardından Go’nun kendi json kütüphanesini json.Unmarshal() diyerek çalıştırırsınız. Böylece tüm json verisi memory’ye alınır ve uygulamanızda kullanılmaya hazırdır.

Peki uygulama içerisinde memory’de kullanıma hazır hale gelen JSON verisinin 4KB değil de 4GB olduğunu düşünelim! Bilgisayarın RAM miktarına falan girip konuyu uzatmaya gerek yok, böyle bir durumda uygulamanız eninde sonunda tabiri caizse patlayacaktır. Çünkü standart json.Unmarshal() fonksiyonu JSON verisini bir bütün halinde okur ancak büyük boyutlu dosyaları doğru şekilde okumanın yolu, JSON Data Streaming ile onu obje obje okumaktır.

Örnek JSON

Konuyu anlatabilmek için bize güzel bir JSON verisi lazım. Bunun için Star Wars filmindeki oyunculardan oluşan bir API’den faydalanalım. The Star Wars API (https://swapi.co/) sitesinin sunduğu şu uca (https://swapi.co/api/people/) tıklayarak JSON verimizi görebilirsiniz.

Tabi buradaki JSON verisi olduğu gibi kullanmadım çünkü sayfalama için Count, Next, Previous gibi şuan için ihtiyacımız olmayan özellikler vardı. Onun yerine çıktıyı, 87 isimin tamamından oluşan, dizi (array) şeklinde tek bir JSON haline getirdim. Bir kişi için genel yapı aşağıdaki gibidir. Tamamına yazının sonunda bağlantısı bulunan, bu yazı için hazırladığım örnek proje dosyalarından ulaşabilirsiniz.

[
    {
        "name": "Luke Skywalker",
        "height": "172",
        "mass": "77",
        "hair_color": "blond",
        "skin_color": "fair",
        "eye_color": "blue",
        "birth_year": "19BBY",
        "gender": "male",
        "homeworld": "https://swapi.co/api/planets/1/",
        "films": [
            "https://swapi.co/api/films/2/",
            "https://swapi.co/api/films/6/",
            "https://swapi.co/api/films/3/",
            "https://swapi.co/api/films/1/",
            "https://swapi.co/api/films/7/"
        ],
        "species": [
            "https://swapi.co/api/species/1/"
        ],
        "vehicles": [
            "https://swapi.co/api/vehicles/14/",
            "https://swapi.co/api/vehicles/30/"
        ],
        "starships": [
            "https://swapi.co/api/starships/12/",
            "https://swapi.co/api/starships/22/"
        ],
        "created": "2014-12-09T13:50:51.644000Z",
        "edited": "2014-12-20T21:17:56.891000Z",
        "url": "https://swapi.co/api/people/1/"
    },
    ...
}

JSON’a Uygun Go Struct

Yukarıdaki yapıya uygun Go Struct’ını elle yazabiliriz ancak onun yerine JSON to Go sitesinden direk karşılığını görebiliriz. Buna göre struct’ımız şu şekildedir.

type People struct {
	Name      string    `json:"name"`
	Height    string    `json:"height"`
	Mass      string    `json:"mass"`
	HairColor string    `json:"hair_color"`
	SkinColor string    `json:"skin_color"`
	EyeColor  string    `json:"eye_color"`
	BirthYear string    `json:"birth_year"`
	Gender    string    `json:"gender"`
	Homeworld string    `json:"homeworld"`
	Films     []string  `json:"films"`
	Species   []string  `json:"species"`
	Vehicles  []string  `json:"vehicles"`
	Starships []string  `json:"starships"`
	Created   time.Time `json:"created"`
	Edited    time.Time `json:"edited"`
	URL       string    `json:"url"`
}

Normal Method ile JSON Okuma

JSON ve Struct’ımızı oluşturduktan sonra artık gerekli Go kodumuzu yazabiliriz. Aşağıdaki kod ile people.json dosyasındaki JSON verisini okuyup, kişilerin istediğimiz özelliğini ekrana yazdırabiliriz.

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"time"
)

var err error

type People struct {
	Name      string    `json:"name"`
	Height    string    `json:"height"`
	Mass      string    `json:"mass"`
	HairColor string    `json:"hair_color"`
	SkinColor string    `json:"skin_color"`
	EyeColor  string    `json:"eye_color"`
	BirthYear string    `json:"birth_year"`
	Gender    string    `json:"gender"`
	Homeworld string    `json:"homeworld"`
	Films     []string  `json:"films"`
	Species   []string  `json:"species"`
	Vehicles  []string  `json:"vehicles"`
	Starships []string  `json:"starships"`
	Created   time.Time `json:"created"`
	Edited    time.Time `json:"edited"`
	URL       string    `json:"url"`
}

func main() {
	// JSON dosyasını açalım.
	jsonDosya, err := os.Open("people.json")
	if err != nil {
		fmt.Println(err)
	}
	defer jsonDosya.Close()

	// Dosya içeriğini okuyalım.
	icerik, err := ioutil.ReadAll(jsonDosya)
	if err != nil {
		fmt.Println(err)
	}

	// Dosyadaki veriyi ayrıştırıp değişkenimize aktaralım.
	var people []People
	json.Unmarshal(icerik, &people)

	// JSON verisindeki isimleri ekrana yazdıralım.
	for _, person := range people {
		fmt.Println(person.Name)
	}
}

JSON Data Streaming Methodu ile Okuma

Büyük boyutlu dosyaları okumada temel olarak 2 fark bulunmaktadır.
1. json.Unmarshall() yerine, json.NewDecoder() kullanılır.
2. JSON’ı okuyup atayacağımız değişken dizi (array) değil, obje obje okuyacağımız için tekil struct olur.

Aynı JSON verisini obje obje okuyarak, her obje üzerinde işlem yapmak için aşağıdaki Go kodunu kullanabiliriz.

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"time"
)

var err error

type People struct {
	Name      string    `json:"name"`
	Height    string    `json:"height"`
	Mass      string    `json:"mass"`
	HairColor string    `json:"hair_color"`
	SkinColor string    `json:"skin_color"`
	EyeColor  string    `json:"eye_color"`
	BirthYear string    `json:"birth_year"`
	Gender    string    `json:"gender"`
	Homeworld string    `json:"homeworld"`
	Films     []string  `json:"films"`
	Species   []string  `json:"species"`
	Vehicles  []string  `json:"vehicles"`
	Starships []string  `json:"starships"`
	Created   time.Time `json:"created"`
	Edited    time.Time `json:"edited"`
	URL       string    `json:"url"`
}

func main() {
	// JSON dosyasını açalım.
	jsonDosya, err := os.Open("people.json")
	if err != nil {
		fmt.Println(err)
	}
	defer jsonDosya.Close()

	dec := json.NewDecoder(jsonDosya)

	// Başlangıç verisini ([) okuyalım
	_, err = dec.Token()
	if err != nil {
		log.Println(err)
	}

	var person People
	// Bu döngü okunacak bir sonraki obje var ise devam eder.
	for dec.More() {
		err = dec.Decode(&person)
		if err != nil {
			log.Println(err)
		}

		fmt.Println(person.Name)
	}

	// Kapanış verisini (]) okuyalım
	_, err = dec.Token()
	if err != nil {
		log.Println(err)
	}
}

Makale Dosyaları

Bu makale için hazırladığım örnek proje dosyalarına şu Github bağlantısından ulaşabilirsiniz:

https://github.com/yakuter/go-buyuk-json-okuma

Son Sözler

Uzun bir aradan sonra yeniden bir Go makalesi ile sizlerle bir araya geldik. Kısa bir JSON okumadan ziyade, büyük boyutlu dosyalara bir çözüm getirerek konuyu biraz da ihtiyacı karşılar hale getirmeye çalıştım. Umarım faydalı olmuştur.

Erhan Yakut Software Developer @Binalyze | Founder @Passwall | Golang Enthusiast | Open Sorcerer