De- and encode interface{} with Gob
Asked Answered
O

2

17

I'm trying to de- and encode a struct which contains a Interface{} as field.

The problem there is, that the encoding works fine, but if I try to decode the data to data the value gets { <nil>}.

It actually works, if I change Data interface{} to Data substring, but this is not a solution for me because I want to cache the results of a query to a database which have different types depending on the query. (e.g. Users or Cookies)

Minimal working example

Source

http://play.golang.org/p/aX7MIfqrWl

package main

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

type Data struct {
    Name string
    Data interface{}
}

type SubType struct {
    Foo string
}

func main() {
    // Encode
    encodeData := Data{
        Name: "FooBar",
        Data: SubType{Foo: "Test"},
    }
    mCache := new(bytes.Buffer)
    encCache := gob.NewEncoder(mCache)
    encCache.Encode(encodeData)

    fmt.Printf("Encoded: ")
    fmt.Println(mCache.Bytes())

    // Decode
    var data Data
    pCache := bytes.NewBuffer(mCache.Bytes())
    decCache := gob.NewDecoder(pCache)
    decCache.Decode(&data)

    fmt.Printf("Decoded: ")
    fmt.Println(data)
}

Outputs

Expected output

Encoded: [37 255 129 3 1 1 4 68 97 116 97 1 255 130 0 1 2 1 4 78 97 109 101 1 12 0 1 4 68 97 116 97 1 255 132 0 0 0 29 255 131 3 1 1 7 83 117 98 84 121 112 101 1 255 132 0 1 1 1 3 70 111 111 1 12 0 0 0 19 255 130 1 6 70 111 111 66 97 114 1 1 4 84 101 115 116 0 0]

Decoded: {FooBar {Test}}

Current Result

Encoded: [37 255 129 3 1 1 4 68 97 116 97 1 255 130 0 1 2 1 4 78 97 109 101 1 12 0 1 4 68 97 116 97 1 255 132 0 0 0 29 255 131 3 1 1 7 83 117 98 84 121 112 101 1 255 132 0 1 1 1 3 70 111 111 1 12 0 0 0 19 255 130 1 6 70 111 111 66 97 114 1 1 4 84 101 115 116 0 0]

Decoded: { }

Octuple answered 2/1, 2013 at 11:41 Comment(3)
How is the decoder supposed to determine the type into which to decode ?Dumbfound
var data Data and decCache.Decode(&data) should handle this, or am I missing something here?Octuple
This gives the address into which to decode, but not the type if Data is an interface{}.Dumbfound
F
-7

You can't decode into an interface because the decoder has no way to determine what type the field should be.

You can handle this in a few different ways. One is to have Data hold a struct with a field for every type that could be decoded. But the type could be very complicated.

The other way is to implement the GobDecoder and GobEncoder interface for your struct and implement your own serialization for the types. This is probably not ideal though.

Perhaps the best approach is to have the cache store specific types instead and use separate method for each type. To use your example. Your application would have a cache method called GetSubType(key string) (*SubType, error) on the cache. This would return the concrete type or a decoding error instead of an interface. It would be cleaner and more readable as well as more typesafe.

Farinaceous answered 2/1, 2013 at 19:5 Comment(2)
The third approach (GetSubType) sounds absolutely reasonable to me. Thanks!Octuple
Downvoting because this answer is wrong; Dmitri's answer shows the very simple approach to make it work.Perceptive
E
44

The problem is that in your code, there is an error when executing encCache.Encode(encodeData) but since you don't check for error, you don't realize that. The output is blank because encodedData fails to get encoded properly.

If you add error checking,

err := enc.Encode(encodeData)
if err != nil {
    log.Fatal("encode error:", err)
}

Then you'd see something like

2013/03/09 17:57:23 encode error:gob: type not registered for interface: main.SubType

If you add one line to your original code before enc.Encode(encodeData),

gob.Register(SubType{})

Then you get expected output.

Decoded: {FooBar {Test}}

See http://play.golang.org/p/xt4zNyPZ2W

Encrust answered 9/3, 2013 at 23:6 Comment(0)
F
-7

You can't decode into an interface because the decoder has no way to determine what type the field should be.

You can handle this in a few different ways. One is to have Data hold a struct with a field for every type that could be decoded. But the type could be very complicated.

The other way is to implement the GobDecoder and GobEncoder interface for your struct and implement your own serialization for the types. This is probably not ideal though.

Perhaps the best approach is to have the cache store specific types instead and use separate method for each type. To use your example. Your application would have a cache method called GetSubType(key string) (*SubType, error) on the cache. This would return the concrete type or a decoding error instead of an interface. It would be cleaner and more readable as well as more typesafe.

Farinaceous answered 2/1, 2013 at 19:5 Comment(2)
The third approach (GetSubType) sounds absolutely reasonable to me. Thanks!Octuple
Downvoting because this answer is wrong; Dmitri's answer shows the very simple approach to make it work.Perceptive

© 2022 - 2024 — McMap. All rights reserved.