go

Golang GOB deserialization issue


I am trying to understand how the GOB package works in GoLang. For better understanding, I want to serialize a structure into a byte buffer and later deserialize the buffer back into a structure of the same type. I want to write a generic code that should deserialize any type, e.g. Product or Company or Item or WebOffer structs. I want to use this approach because different kind of data are streamed and the program does not know the actual type of data.

Currently I am using the Product structure and I do not want to specify the type of the structure at the time of deserialization. If you see the below commented code which works as expected because I have passed the actual type of the structure for the deserialization.

var p1 Product

err = deserializeGob(data, &p1)

On the other hand, if I use -

decodedData, err := deserializeGob_AnotherWay(data)

the above function call throws an error - Error deserializing: error deserializing: gob: local interface type *interface {} can only be decoded from remote interface type; received concrete type Product = struct { Name string; Price float; Discount ProductDiscount = struct { Percentage float; }; }

I could not find the cause of this error. I would appreciate your help.

Complete code -

package main

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

// Product struct
type Product struct {
    Name           string
    Price          float64
    Discount ProductDiscount
}

// ProductDiscount struct
type ProductDiscount struct {
    Percentage float64
}


func serialize(prod interface{}) ([]byte, error) {
    buf := new(bytes.Buffer)
    enc := gob.NewEncoder(buf)
    err := enc.Encode(prod)
    if err != nil {
        return nil, fmt.Errorf("error serializing: %w", err)
    }
    return buf.Bytes(), nil
}

/*func deserializeGob(data []byte, v interface{}) (error) {
    buf := bytes.NewReader(data)
    dec := gob.NewDecoder(buf)
    err := dec.Decode(v)
    if err != nil {
        return fmt.Errorf("error deserializing: %w", err)
    }
    return nil
}*/

func deserializeGob_AnotherWay(data []byte) (interface{}, error) {
    buf := bytes.NewReader(data)
    dec := gob.NewDecoder(buf)
    var v interface{}
    err := dec.Decode(&v)
    if err != nil {
        return nil, fmt.Errorf("error deserializing: %w", err)
    }

    // Type assertion to get the original Product type
    switch v := v.(type) {
    case Product:
        return v, nil
    default:
        return nil, fmt.Errorf("unexpected type: %T", v)
    }
}

func main() {
    var d ProductDiscount
    d.Percentage = 10
    prod := Product{
        Name:     "CarToy1",
        Price:      30,
        Discount: d,
    }

    data, err := serialize(prod)
    if err != nil {
        fmt.Println("Error serializing:", err)
        return
    }

    fmt.Println("Serialized data:", data)
    
    /*var p1 Product
    err = deserializeGob(data, &p1)
    if err != nil {
        fmt.Println("Error deserializing:", err)
        return
    }
    fmt.Println("Deserialized product:", p1)*/

    decodedData, err := deserializeGob_AnotherWay(data)
    if err != nil {
        fmt.Println("Error deserializing:", err)
        return
    }

    // Type assertion to get the original Product type
    p2, ok := decodedData.(Product)
    if !ok {
        fmt.Println("Deserialized data is not of type product")
        return
    }

    fmt.Println("Deserialized product:", p2)
}

Solution

  • It was missing a derefencing before the enc.Encode(&prod) and also the struct must be registered with gob.Register(Product{}) both for serialization and deserialization.

    package main
    
    import (
        "bytes"
        "encoding/gob"
        "fmt"
    )
    
    // Product struct
    type Product struct {
        Name     string
        Price    float64
        Discount ProductDiscount
    }
    
    // ProductDiscount struct
    type ProductDiscount struct {
        Percentage float64
    }
    
    func serialize(prod interface{}) ([]byte, error) {
        buf := new(bytes.Buffer)
        enc := gob.NewEncoder(buf)
        err := enc.Encode(&prod)
        if err != nil {
            return nil, fmt.Errorf("error serializing: %w", err)
        }
        return buf.Bytes(), nil
    }
    
    func deserializeGob_AnotherWay(data []byte) (interface{}, error) {
        buf := bytes.NewReader(data)
        dec := gob.NewDecoder(buf)
        var v interface{}
        err := dec.Decode(&v)
        if err != nil {
            return nil, fmt.Errorf("error deserializing: %w", err)
        }
    
        // Type assertion to get the original Product type
        switch v := v.(type) {
        case Product:
            return v, nil
        default:
            return nil, fmt.Errorf("unexpected type: %T", v)
        }
    }
    
    func main() {
        var d ProductDiscount
        d.Percentage = 10
        prod := Product{
            Name:     "CarToy1",
            Price:    30,
            Discount: d,
        }
    
        gob.Register(Product{})
        data, err := serialize(prod)
        if err != nil {
            fmt.Println("Error serializing:", err)
            return
        }
    
        fmt.Println("Serialized data:", data)
    
        decodedData, err := deserializeGob_AnotherWay(data)
        if err != nil {
            fmt.Println("Error deserializing:", err)
            return
        }
    
        // Type assertion to get the original Product type
        p2, ok := decodedData.(Product)
        if !ok {
            fmt.Println("Deserialized data is not of type product")
            return
        }
    
        fmt.Println("Deserialized product:", p2)
    }
    

    You might also make it a bit shorter with the new any generics type.

    package main
    
    import (
        "bytes"
        "encoding/gob"
        "fmt"
    )
    
    // Product struct
    type Product struct {
        Name     string
        Price    float64
        Discount ProductDiscount
    }
    
    // ProductDiscount struct
    type ProductDiscount struct {
        Percentage float64
    }
    
    func serialize(prod any) ([]byte, error) {
        buf := new(bytes.Buffer)
        enc := gob.NewEncoder(buf)
        err := enc.Encode(&prod)
        if err != nil {
            return nil, fmt.Errorf("error serializing: %w", err)
        }
        return buf.Bytes(), nil
    }
    
    func deserializeGob_AnotherWay(data []byte) (any, error) {
        buf := bytes.NewReader(data)
        dec := gob.NewDecoder(buf)
        var v any
        err := dec.Decode(&v)
        if err != nil {
            return nil, fmt.Errorf("error deserializing: %w", err)
        }
        return v, err
    }
    
    func main() {
        var d ProductDiscount
        d.Percentage = 10
        prod := Product{
            Name:     "CarToy1",
            Price:    30,
            Discount: d,
        }
    
        gob.Register(Product{})
        data, err := serialize(prod)
        if err != nil {
            fmt.Println("Error serializing:", err)
            return
        }
    
        fmt.Println("Serialized data:", data)
    
        decodedData, err := deserializeGob_AnotherWay(data)
        if err != nil {
            fmt.Println("Error deserializing:", err)
            return
        }
    
        // Type assertion to get the original Product type
        p2, ok := decodedData.(Product)
        if !ok {
            fmt.Println("Deserialized data is not of type product")
            return
        }
    
        fmt.Println("Deserialized product:", p2)
    }