1. encoding/json
standard kütüphanesinin genel bir bakışı
Go dilinin sağlam bir encoding/json
kütüphanesi, JSON veri formatını işlemek için sunulmuştur. Bu kütüphane ile Go veri tiplerini JSON formatına kolayca dönüştürebilirsiniz (serileştirme) veya JSON verisini Go veri tiplerine dönüştürebilirsiniz (serileştirmeyi tersine çevirme). Bu kütüphane, kodlama, kod çözme, akışlı giriş/çıkış ve özel JSON ayrıştırma mantığını destekleme gibi birçok işlev sağlar.
Bu kütüphanedeki en önemli veri tipleri ve fonksiyonlar şunlardır:
-
Marshal
veMarshalIndent
: Go veri tiplerini JSON dizelerine serileştirmek için kullanılır. -
Unmarshal
: JSON dizelerini Go veri tiplerine tersine çevirmek için kullanılır. -
Encoder
veDecoder
: JSON verisinin akışlı giriş/çıkışı için kullanılır. -
Valid
: Verilen bir dizenin geçerli bir JSON formatı olup olmadığını kontrol etmek için kullanılır.
Bu fonksiyonların ve tiplerin kullanımını gelecek bölümlerde öğreneceğiz.
2. Go veri yapılarını JSON'a serileştirme
2.1 json.Marshal
Kullanımı
json.Marshal
, Go veri tiplerini JSON dizelerine serileştiren bir fonksiyondur. Bu fonksiyon, Go dilindeki veri tiplerini girdi olarak alır, bunları JSON formatına dönüştürür ve olası hatalarla birlikte bir byte dizisi döndürür.
İşte bir Go yapısını JSON dizesine dönüştürmenin nasıl yapıldığını gösteren basit bir örnek:
package main
import (
"encoding/json"
"fmt"
"log"
)
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
person := Person{"Alice", 30}
jsonData, err := json.Marshal(person)
if err != nil {
log.Fatalf("JSON serileştirme başarısız oldu: %s", err)
}
fmt.Println(string(jsonData)) // Çıktı: {"name":"Alice","age":30}
}
Yapılarla birlikte, json.Marshal
fonksiyonu ayrıca harita ve dilim gibi diğer veri tiplerini de serileştirebilir. Aşağıda map[string]interface{}
ve dilim
kullanarak örnekler verilmiştir:
// Haritayı JSON'a dönüştürme
myMap := map[string]interface{}{
"name": "Bob",
"age": 25,
}
jsonData, err := json.Marshal(myMap)
// ... hata işleme ve çıktı atlanmış ...
// Dilimi JSON'a dönüştürme
mySlice := []string{"Elma", "Muz", "Kiraz"}
jsonData, err := json.Marshal(mySlice)
// ... hata işleme ve çıktı atlanmış ...
2.2 Yapı Etiketleri
Go'da, yapı etiketleri yapı alanları için meta veri sağlamak için kullanılır ve JSON serileştirmenin davranışını kontrol eder. En yaygın kullanım alanları alan yeniden adlandırma, alanları yoksayma ve koşullu serileştirme içerir.
Örneğin, JSON alanının adını belirtmek için json:"<name>"
etiketini kullanabilirsiniz:
type Animal struct {
SpeciesName string `json:"species"`
Description string `json:"desc,omitempty"`
Tag string `json:"-"` // "+" etiketi, bu alanın serileştirilmeyeceğini belirtir
}
Yukarıdaki örnekte, Tag
alanının önündeki json:"-"
etiketi, json.Marshal
'a bu alanı yok sayması gerektiğini söyler. Description
alanı için omitempty
seçeneği, alanın boş (sıfır değer, boş dize gibi) ise serileştirilmiyeceğini belirtir.
İşte yapı etiketlerini kullanan tam bir örnek:
package main
import (
"encoding/json"
"fmt"
"log"
)
type Animal struct {
SpeciesName string `json:"species"`
Description string `json:"desc,omitempty"`
Tag string `json:"-"`
}
func main() {
animal := Animal{
SpeciesName: "Afrika Fil",
Description: "Bir fil ile gövdesi ve dişleri olan büyük bir memeli.",
Tag: "nesli tükenmekte olan", // Bu alan JSON'a serileştirilmeyecek
}
jsonData, err := json.Marshal(animal)
if err != nil {
log.Fatalf("JSON serileştirme başarısız oldu: %s", err)
}
fmt.Println(string(jsonData)) // Çıktı: {"species":"Afrika Fil","desc":"Bir fil ile gövdesi ve dişleri olan büyük bir memeli."}
}
Bu şekilde, JSON temsilini esnek bir şekilde kontrol ederek net bir veri yapısı sağlayabilirsiniz ve çeşitli serileştirme ihtiyaçlarını esnek bir şekilde ele alabilirsiniz.
3. JSON'u Go Veri Yapısına Tersine Çevirme
3.1 json.Unmarshal
Kullanımı
json.Unmarshal
işlevi, Go veri yapıları olan struct'lar, map'ler vb. gibi JSON dizelerini parse etmemize olanak tanır. json.Unmarshal
kullanabilmek için öncelikle JSON verileri ile eşleşen bir Go veri yapısı tanımlamamız gerekmektedir.
Aşağıdaki JSON verilerimiz olduğunu varsayalım:
{
"name": "Alice",
"age": 25,
"emails": ["[email protected]", "[email protected]"]
}
Bu verileri Go struct'ına parse etmek için uygun bir struct tanımlamamız gerekmektedir:
type User struct {
Name string `json:"name"`
Age int `json:"age"`
Emails []string `json:"emails"`
}
Şimdi deserialization için json.Unmarshal
kullanabiliriz:
import (
"encoding/json"
"fmt"
)
func main() {
jsonData := `{
"name": "Alice",
"age": 25,
"emails": ["[email protected]", "[email protected]"]
}`
var user User
err := json.Unmarshal([]byte(jsonData), &user)
if err != nil {
fmt.Println("JSON'un parse edilmesinde hata oluştu:", err)
return
}
fmt.Printf("Kullanıcı: %+v\n", user)
}
Yukarıdaki örnekte json:"name"
gibi etiketleri kullanarak json.Unmarshal
işlevini JSON alanlarının struct alanlarına eşleşmesi konusunda bilgilendirdik.
3.2 Dinamik Parse Etme
Bazı durumlarda, parse etmemiz gereken JSON yapısı önceden bilinmiyor olabilir ya da JSON verilerinin yapısı dinamik olarak değişebilir. Bu tür durumlarda interface{}
ya da json.RawMessage
kullanabiliriz.
interface{}
kullanarak JSON yapısını bilmeden parse etmemize olanak tanır:
func main() {
jsonData := `{
"name": "Alice",
"details": {
"age": 25,
"job": "Mühendis"
}
}`
var result map[string]interface{}
json.Unmarshal([]byte(jsonData), &result)
fmt.Println(result)
// Tür denetimleri, kullanmadan önce tür eşleşmesini sağlar
name := result["name"].(string)
fmt.Println("İsim:", name)
details := result["details"].(map[string]interface{})
age := details["age"].(float64) // Not: interface{} içindeki sayılar float64 olarak işlenir
fmt.Println("Yaş:", age)
}
json.RawMessage
kullanarak ise orijinal JSON'u korurken belirli bölümlerini seçerek parse etmemize olanak tanır:
type UserDynamic struct {
Name string `json:"name"`
Details json.RawMessage `json:"details"`
}
func main() {
jsonData := `{
"name": "Alice",
"details": {
"age": 25,
"job": "Mühendis"
}
}`
var user UserDynamic
json.Unmarshal([]byte(jsonData), &user)
var details map[string]interface{}
json.Unmarshal(user.Details, &details)
fmt.Println("İsim:", user.Name)
fmt.Println("Yaş:", details["age"])
fmt.Println("Meslek:", details["job"])
}
Bu yaklaşım, belirli alanların farklı türlerde veri içerebileceği JSON yapılarını ele almak için kullanışlıdır ve esnek veri işleme imkanı sağlar.
4. İç İçe Yapılar ve Dizilerle Başa Çıkma
4.1 İç İçe Geçmiş JSON Objeler
Yaygın JSON verileri genellikle düz değil, iç içe geçmiş yapılar içerir. Go'da, bu durumu iç içe geçmiş struct'ları tanımlayarak ele alabiliriz.
Aşağıdaki şekilde iç içe geçmiş JSON'un olduğunu varsayalım:
{
"name": "Bob",
"contact": {
"email": "[email protected]",
"address": "123 Main St"
}
}
Bu durumu iç içe geçmiş struct'lar tanımlayarak ele alabiliriz:
type ContactInfo struct {
Email string `json:"email"`
Address string `json:"address"`
}
type UserWithContact struct {
Name string `json:"name"`
Contact ContactInfo `json:"contact"`
}
Deserialization işlemi, iç içe geçmiş olmayan yapılar için benzer şekilde gerçekleştirilir:
func main() {
jsonData := `{
"name": "Bob",
"contact": {
"email": "[email protected]",
"address": "123 Main St"
}
}`
var user UserWithContact
err := json.Unmarshal([]byte(jsonData), &user)
if err != nil {
fmt.Println("JSON'un parse edilmesinde hata oluştu:", err)
}
fmt.Printf("%+v\n", user)
}
4.2 JSON Dizileri
JSON'da diziler yaygın bir veri yapısıdır. Go dilinde bunlar dilimlere karşılık gelir.
Aşağıdaki JSON dizisini ele alalım:
[
{"name": "Dave", "age": 34},
{"name": "Eve", "age": 28}
]
Go dilinde karşılık gelen yapıları ve dilimleri şu şekilde tanımlarız:
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
jsonData := `[
{"name": "Dave", "age": 34},
{"name": "Eve", "age": 28}
]`
var people []Person
json.Unmarshal([]byte(jsonData), &people)
for _, person := range people {
fmt.Printf("%+v\n", person)
}
}
Bu şekilde, JSON dizisindeki her öğeyi Go dilimine dönüştürerek daha sonra işleme ve erişime uygun Go yapılarının bir dilimini elde edebiliriz.
5 Hata Yönetimi
JSON verileriyle uğraşırken, yapılandırılmış veriyi JSON biçimine dönüştürme (serileştirme) veya JSON'u yapılandırılmış veriye dönüştürme (serileştirmeyi geri alma) olsun, hatalar meydana gelebilir. Şimdi, yaygın hataları ve bunlarla nasıl başa çıkılacağını tartışacağız.
5.1 Serileştirme Hata Yönetimi
Serileştirme hataları genellikle bir struct veya diğer veri tiplerini JSON dizesine dönüştürme sürecinde ortaya çıkar. Örneğin, JSON'da temsil edilemeyen (örneğin bir kanal tipi veya JSON'da temsil edilemeyen bir işlev gibi) yasadışı alanlar içeren bir struct'ı serileştirmeye çalışmak durumunda, json.Marshal
bir hata döndürecektir.
import (
"encoding/json"
"fmt"
"log"
)
type User struct {
Name string
Age int
// JSON'da temsil edilemeyen bir alan olduğunu varsayalım
// Data chan struct{} // Kanallar JSON'da temsil edilemez
}
func main() {
u := User{
Name: "Alice",
Age: 30,
// Data: make(chan struct{}),
}
bytes, err := json.Marshal(u)
if err != nil {
log.Fatalf("JSON serileştirmesi başarısız oldu: %v", err)
}
fmt.Println(string(bytes))
}
Yukarıdaki örnekte, niyetlice Data
alanını yorum satırına aldık. Yorum satırından çıkarıldığında, serileştirme başarısız olacak ve program hata mesajını günlüğe kaydederek çalışmayı sonlandıracaktır. Bu tür hataların işlenmesi genellikle hata kontrolü yaparak ve ilgili hata yönetimi stratejilerini (örneğin, hataları günlüğe kaydetme, varsayılan veri döndürme vb.) uygulayarak gerçekleşir.
5.2 Deserileştirme Hata Yönetimi
Deserileştirme hataları, bir JSON dizesini geriye dönüştürerek bir Go struct veya diğer veri tiplerine dönüştürme sürecinde meydana gelebilir. Örneğin, JSON dizesi biçimi yanlış veya hedef türle uyumsuz olduğunda, json.Unmarshal
bir hata döndürecektir.
import (
"encoding/json"
"fmt"
"log"
)
func main() {
var data = []byte(`{"name":"Alice","age":"unknown"}`) // "age" bir tamsayı olmalıdır, ancak burada bir dize sağlandı
var u User
err := json.Unmarshal(data, &u)
if err != nil {
log.Fatalf("JSON deserileştirmesi başarısız oldu: %v", err)
}
fmt.Printf("%+v\n", u)
}
Bu kod örneğinde, niyetlice age
alanı için beklenen tamsayı yerine yanlış veri türü sağladık, bu da json.Unmarshal
'ın bir hata fırlatmasına neden oldu. Bu nedenle, bu durumu uygun şekilde ele almalıyız. Yaygın uygulama, hata mesajını günlüğe kaydetmek ve duruma bağlı olarak boş bir nesne, varsayılan değer veya hata mesajı döndürmek olacaktır.
6 Gelişmiş Özellikler ve Performans Optimizasyonu
6.1 Özel Marshal ve Unmarshal
Go'daki encoding/json
paketi varsayılan olarak JSON'u yansıma yoluyla serileştirir ve serileştirir. Bununla birlikte, json.Marshaler
ve json.Unmarshaler
arabirimlerini uygulayarak bu işlemleri özelleştirebiliriz.
import (
"encoding/json"
"fmt"
)
type Color struct {
Red uint8
Green uint8
Blue uint8
}
func (c Color) MarshalJSON() ([]byte, error) {
hex := fmt.Sprintf("\"#%02x%02x%02x\"", c.Red, c.Green, c.Blue)
return []byte(hex), nil
}
func (c *Color) UnmarshalJSON(data []byte) error {
_, err := fmt.Sscanf(string(data), "\"#%02x%02x%02x\"", &c.Red, &c.Green, &c.Blue)
return err
}
func main() {
c := Color{Red: 255, Green: 99, Blue: 71}
jsonColor, _ := json.Marshal(c)
fmt.Println(string(jsonColor))
var newColor Color
json.Unmarshal(jsonColor, &newColor)
fmt.Println(newColor)
}
Burada Color
türünü tanımladık ve RGB renklerini ondalık altıgen dizgelere dönüştürmek ve sonra geri RGB renklerine döndürmek için MarshalJSON
ve UnmarshalJSON
yöntemlerini uyguladık.
6.2 Kodlayıcı ve Kod Çözücüler
Büyük JSON verileriyle uğraşırken, doğrudan json.Marshal
ve json.Unmarshal
kullanmak aşırı bellek tüketimine veya verimsiz giriş/çıkış işlemlerine neden olabilir. Bu nedenle, Go'daki encoding/json
paketi, JSON verilerini akış şeklinde işleyebilen Encoder
ve Decoder
türlerini sağlar.
6.2.1 json.Encoder
Kullanımı
json.Encoder
, io.Writer arabirimini uygulayan herhangi bir nesneye doğrudan JSON verilerini yazabilir, bu da JSON verilerini dosyaya, ağ bağlantısına vb. doğrudan kodlayabileceğiniz anlamına gelir.
import (
"encoding/json"
"os"
)
func main() {
users := []User{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 25},
}
file, _ := os.Create("users.json")
defer file.Close()
encoder := json.NewEncoder(file)
if err := encoder.Encode(users); err != nil {
log.Fatalf("Kodlama hatası: %v", err)
}
}
6.2.2 json.Decoder
Kullanımı
json.Decoder
, io.Reader arabirimini uygulayan herhangi bir nesneden doğrudan JSON verilerini okuyabilir ve JSON nesnelerini ve dizilerini arayabilir ve ayrıştırabilir.
import (
"encoding/json"
"os"
)
func main() {
file, _ := os.Open("users.json")
defer file.Close()
var users []User
decoder := json.NewDecoder(file)
if err := decoder.Decode(&users); err != nil {
log.Fatalf("Çözme hatası: %v", err)
}
for _, u := range users {
fmt.Printf("%+v\n", u)
}
}
Kodlayıcılar ve kod çözücülerle veri işleyerek okurken JSON işlemleri gerçekleştirebilir, bellek kullanımını azaltabilir ve işleme verimliliğini artırabilirsiniz. Bu özellik özellikle ağ transferleri veya büyük dosyaları işlemede çok kullanışlıdır.