Gob

SerializationGoBinary FormatStandard LibraryRPC

Library

Gob - Go Language Binary Serialization Format

Overview

Gob is a binary serialization format included in Go's standard library. Designed specifically for data exchange between Go programs, it adopts a self-describing format that includes type information. Provided as the encoding/gob package, it can efficiently serialize complex data structures (pointers, slices, maps, interfaces, etc.). It is particularly optimized for use in RPC and network communication scenarios.

Details

Gob is a binary encoding format designed by Rob Pike and Ken Thompson specifically for the Go language. Unlike Protocol Buffers or MessagePack, it doesn't require schema definitions and is tightly integrated with Go's type system. By embedding type information in the data stream, the sender and receiver don't need to have exactly the same type definitions.

A distinctive design feature of Gob is its streaming support. Multiple values can be sent and received sequentially over a single connection, with type information included only on the first transmission, making it efficient to transfer multiple values of the same type. It also has some compatibility with field additions and deletions, supporting struct evolution.

Encoding is performed recursively, and pointer circular references are handled appropriately. It also supports interface type values, which are serialized along with their actual type information. However, some types like functions, channels, and unsafe.Pointer cannot be serialized.

Advantages and Disadvantages

Advantages

  • Complete Go Integration: Provided in standard library with no additional dependencies
  • Self-Describing: No schema definition required, automatically includes type information
  • Streaming Support: Efficiently send and receive multiple values
  • Type Safety: Compile-time type checking and runtime type validation
  • Pointer & Circular Reference Support: Correctly serializes complex data structures
  • Version Compatibility: Some support for field additions and deletions

Disadvantages

  • Go-Only: No interoperability with other languages
  • Size Efficiency: Larger than pure binary due to type information
  • Performance: Faster than JSON but slower than specialized serializers
  • Limited Customization: Cannot modify encoding format
  • Type Restrictions: Functions, channels, etc. cannot be serialized
  • Debugging Difficulty: Binary format is not human-readable

References

Code Examples

1. Basic Encode and Decode

package main

import (
    "bytes"
    "encoding/gob"
    "fmt"
    "log"
)

type Person struct {
    Name    string
    Age     int
    Email   string
    Active  bool
}

func main() {
    // Encode
    var buf bytes.Buffer
    encoder := gob.NewEncoder(&buf)
    
    person := Person{
        Name:   "John Doe",
        Age:    30,
        Email:  "[email protected]",
        Active: true,
    }
    
    err := encoder.Encode(person)
    if err != nil {
        log.Fatal("Encode error:", err)
    }
    
    // Decode
    decoder := gob.NewDecoder(&buf)
    var decodedPerson Person
    
    err = decoder.Decode(&decodedPerson)
    if err != nil {
        log.Fatal("Decode error:", err)
    }
    
    fmt.Printf("Decoded: %+v\n", decodedPerson)
}

2. Streaming Multiple Values

package main

import (
    "bytes"
    "encoding/gob"
    "fmt"
    "log"
)

type Message struct {
    ID        int
    Content   string
    Timestamp int64
}

func streamingExample() {
    var network bytes.Buffer // Simulate network connection
    encoder := gob.NewEncoder(&network)
    decoder := gob.NewDecoder(&network)
    
    // Sender: Send multiple messages
    messages := []Message{
        {ID: 1, Content: "First message", Timestamp: 1234567890},
        {ID: 2, Content: "Second message", Timestamp: 1234567891},
        {ID: 3, Content: "Third message", Timestamp: 1234567892},
    }
    
    for _, msg := range messages {
        if err := encoder.Encode(msg); err != nil {
            log.Fatal("Send error:", err)
        }
    }
    
    // Receiver: Read from stream
    for i := 0; i < len(messages); i++ {
        var received Message
        if err := decoder.Decode(&received); err != nil {
            log.Fatal("Receive error:", err)
        }
        fmt.Printf("Received: %+v\n", received)
    }
}

3. Interface Type Serialization

package main

import (
    "bytes"
    "encoding/gob"
    "fmt"
)

// Interface definition
type Animal interface {
    Speak() string
}

type Dog struct {
    Name  string
    Breed string
}

func (d Dog) Speak() string {
    return fmt.Sprintf("%s: Woof!", d.Name)
}

type Cat struct {
    Name  string
    Color string
}

func (c Cat) Speak() string {
    return fmt.Sprintf("%s: Meow!", c.Name)
}

type Zoo struct {
    Animals []Animal
}

func interfaceExample() {
    // Register types (required for interfaces)
    gob.Register(Dog{})
    gob.Register(Cat{})
    
    zoo := Zoo{
        Animals: []Animal{
            Dog{Name: "Buddy", Breed: "Golden Retriever"},
            Cat{Name: "Whiskers", Color: "Orange"},
            Dog{Name: "Max", Breed: "German Shepherd"},
        },
    }
    
    // Encode
    var buf bytes.Buffer
    encoder := gob.NewEncoder(&buf)
    if err := encoder.Encode(zoo); err != nil {
        panic(err)
    }
    
    // Decode
    var decodedZoo Zoo
    decoder := gob.NewDecoder(&buf)
    if err := decoder.Decode(&decodedZoo); err != nil {
        panic(err)
    }
    
    // Call interface methods
    for _, animal := range decodedZoo.Animals {
        fmt.Println(animal.Speak())
    }
}

4. Custom Encoding (GobEncoder/GobDecoder)

package main

import (
    "bytes"
    "encoding/gob"
    "fmt"
    "time"
)

// Custom type
type CustomTime struct {
    time.Time
}

// Implement GobEncoder interface
func (ct CustomTime) GobEncode() ([]byte, error) {
    return []byte(ct.Format(time.RFC3339)), nil
}

// Implement GobDecoder interface
func (ct *CustomTime) GobDecode(data []byte) error {
    t, err := time.Parse(time.RFC3339, string(data))
    if err != nil {
        return err
    }
    ct.Time = t
    return nil
}

type Event struct {
    Name      string
    Timestamp CustomTime
}

func customEncodingExample() {
    event := Event{
        Name:      "System startup",
        Timestamp: CustomTime{time.Now()},
    }
    
    var buf bytes.Buffer
    encoder := gob.NewEncoder(&buf)
    decoder := gob.NewDecoder(&buf)
    
    // Encode (custom encoding is used)
    if err := encoder.Encode(event); err != nil {
        panic(err)
    }
    
    var decoded Event
    if err := decoder.Decode(&decoded); err != nil {
        panic(err)
    }
    
    fmt.Printf("Event: %s, Time: %s\n", 
        decoded.Name, decoded.Timestamp.Format("2006-01-02 15:04:05"))
}

5. Struct Evolution and Version Compatibility

package main

import (
    "bytes"
    "encoding/gob"
    "fmt"
)

// Version 1 struct
type PersonV1 struct {
    Name string
    Age  int
}

// Version 2 struct (fields added)
type PersonV2 struct {
    Name    string
    Age     int
    Email   string // New field
    Phone   string // New field
}

func versionCompatibility() {
    // Encode with V1
    v1 := PersonV1{Name: "Alice Smith", Age: 25}
    var buf bytes.Buffer
    encoder := gob.NewEncoder(&buf)
    encoder.Encode(v1)
    
    // Decode with V2 (new fields get zero values)
    var v2 PersonV2
    decoder := gob.NewDecoder(&buf)
    decoder.Decode(&v2)
    fmt.Printf("V1→V2: %+v\n", v2)
    
    // Encode with V2
    buf.Reset()
    v2Full := PersonV2{
        Name:  "Bob Johnson",
        Age:   30,
        Email: "[email protected]",
        Phone: "555-1234",
    }
    encoder = gob.NewEncoder(&buf)
    encoder.Encode(v2Full)
    
    // Decode with V1 (extra fields are ignored)
    var v1Decoded PersonV1
    decoder = gob.NewDecoder(&buf)
    decoder.Decode(&v1Decoded)
    fmt.Printf("V2→V1: %+v\n", v1Decoded)
}

6. File Save and Load

package main

import (
    "encoding/gob"
    "fmt"
    "os"
)

type GameState struct {
    Level      int
    Score      int64
    PlayerName string
    Inventory  map[string]int
    Position   struct {
        X, Y, Z float64
    }
}

func saveToFile(filename string, state GameState) error {
    file, err := os.Create(filename)
    if err != nil {
        return err
    }
    defer file.Close()
    
    encoder := gob.NewEncoder(file)
    return encoder.Encode(state)
}

func loadFromFile(filename string) (GameState, error) {
    var state GameState
    
    file, err := os.Open(filename)
    if err != nil {
        return state, err
    }
    defer file.Close()
    
    decoder := gob.NewDecoder(file)
    err = decoder.Decode(&state)
    return state, err
}

func fileExample() {
    gameState := GameState{
        Level:      5,
        Score:      12500,
        PlayerName: "Player1",
        Inventory: map[string]int{
            "Potion": 3,
            "Elixir": 1,
            "Key":    5,
        },
        Position: struct{ X, Y, Z float64 }{100.5, 50.0, -25.3},
    }
    
    // Save
    if err := saveToFile("game.gob", gameState); err != nil {
        panic(err)
    }
    
    // Load
    loaded, err := loadFromFile("game.gob")
    if err != nil {
        panic(err)
    }
    
    fmt.Printf("Loaded game state: %+v\n", loaded)
}