Golang Gob decoding does not decode array of []byte

Issue

I am trying to decode an Inv struct, but decoding the same encoded value returns a different value.

// inv struct
type Inv struct {
    AddrFrom string
    Type     int
    data     [][]byte  
}


inv := Inv{
    AddrFrom: nodeAddress,
    Type:     kind,
    data:     inventories,
}
data := GobEncode(inv)
var payload Inv
gob.NewDecoder(bytes.NewBuffer(data)).Decode(&payload)

Here payload and inv have different values. When decoded data field of inv struct is of length zero.

Solution

https://pkg.go.dev/encoding/gob

A struct field of chan or func type is treated exactly like an unexported field and is ignored.

https://go.dev/ref/spec#Exported_identifiers

An identifier may be exported to permit access to it from another package. An identifier is exported if both:

  • the first character of the identifier’s name is a Unicode upper case letter (Unicode class "Lu"); and
  • the identifier is declared in the package block or it is a field name or method name.

All other identifiers are not exported.

https://pkg.go.dev/encoding/gob#hdr-Types_and_Values

Gob can encode a value of any type implementing the GobEncoder or encoding.BinaryMarshaler interfaces by calling the corresponding method, in that order of preference.

Internally, the gob package relies on the reflect package, which is designed to respect the visibility principle. Thus the gob package does not handle those fields automatically, it requires you to write dedicated implementation.


https://pkg.go.dev/encoding/gob#GobEncoder

GobEncoder is the interface describing data that provides its own representation for encoding values for transmission to a GobDecoder. A type that implements GobEncoder and GobDecoder has complete control over the representation of its data and may therefore contain things such as private fields, channels, and functions, which are not usually transmissible in gob streams.

Example

package main

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

// The Vector type has unexported fields, which the package cannot access.
// We therefore write a BinaryMarshal/BinaryUnmarshal method pair to allow us
// to send and receive the type with the gob package. These interfaces are
// defined in the "encoding" package.
// We could equivalently use the locally defined GobEncode/GobDecoder
// interfaces.
type Vector struct {
    x, y, z int
}

func (v Vector) MarshalBinary() ([]byte, error) {
    // A simple encoding: plain text.
    var b bytes.Buffer
    fmt.Fprintln(&b, v.x, v.y, v.z)
    return b.Bytes(), nil
}

// UnmarshalBinary modifies the receiver so it must take a pointer receiver.
func (v *Vector) UnmarshalBinary(data []byte) error {
    // A simple encoding: plain text.
    b := bytes.NewBuffer(data)
    _, err := fmt.Fscanln(b, &v.x, &v.y, &v.z)
    return err
}

// This example transmits a value that implements the custom encoding and decoding methods.
func main() {
    var network bytes.Buffer // Stand-in for the network.

    // Create an encoder and send a value.
    enc := gob.NewEncoder(&network)
    err := enc.Encode(Vector{3, 4, 5})
    if err != nil {
        log.Fatal("encode:", err)
    }

    // Create a decoder and receive a value.
    dec := gob.NewDecoder(&network)
    var v Vector
    err = dec.Decode(&v)
    if err != nil {
        log.Fatal("decode:", err)
    }
    fmt.Println(v)

}

As the field type is already a byte slice, you really are just hitting a visibility access issue and the dedicated required marshalling implementation, while arguable because you could as well export that field, should be straightforward.

Answered By – mh-cbon

Answer Checked By – Clifford M. (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.