在不知道具体类型的情况下解码gob输出
我正在使用gob将结构序列化到磁盘.有问题的结构包含一个接口字段,因此具体类型需要使用gob.Register(...)
注册.
I'm using gob to serialize structs to disk. The struct in question contains an interface field, so the concrete type needs to be registered using gob.Register(...)
.
这里的难题是,进行gob-ing的库应该对所使用的具体类型一无所知.我希望即使调用者定义了自己的接口实现,也可以进行序列化.
The wrinkle here is that the library doing the gob-ing should be ignorant of the concrete type in use. I wanted the serialization to be possible even when callers have defined their own implementations of the interface.
我可以通过动态注册类型来成功编码数据(请参见下面的简单示例),但是在尝试重新读取数据时,gob拒绝接受未注册的类型.令人沮丧的是,因为感觉好像所有数据都在那里-如果gob标记为这样,为什么不将gob仅仅解压缩为main.UpperCaseTransformation
结构?
I can successfully encode the data by registering the type on the fly (see trivial example below), but upon trying to re-read that data, gob refuses to accept the un-registered type. Its frustrating, because it feels like all the data is there - why isn't gob just unpacking that as a main.UpperCaseTransformation
struct if it's labelled as such?
package main
import (
"encoding/gob"
"fmt"
"os"
"strings"
)
type Transformation interface {
Transform(s string) string
}
type TextTransformation struct {
BaseString string
Transformation Transformation
}
type UpperCaseTransformation struct{}
func (UpperCaseTransformation) Transform(s string) string {
return strings.ToUpper(s)
}
func panicOnError(err error) {
if err != nil {
panic(err)
}
}
// Execute this twice to see the problem (it will tidy up files)
func main() {
file := os.TempDir() + "/so-example"
if _, err := os.Stat(file); os.IsNotExist(err) {
tt := TextTransformation{"Hello, World!", UpperCaseTransformation{}}
// Note: didn't need to refer to concrete type explicitly
gob.Register(tt.Transformation)
f, err := os.Create(file)
panicOnError(err)
defer f.Close()
enc := gob.NewEncoder(f)
err = enc.Encode(tt)
panicOnError(err)
fmt.Println("Run complete, run again for error.")
} else {
f, err := os.Open(file)
panicOnError(err)
defer os.Remove(f.Name())
defer f.Close()
var newTT TextTransformation
dec := gob.NewDecoder(f)
// Errors with: `gob: name not registered for interface: "main.UpperCaseTransformation"'
err = dec.Decode(&newTT)
panicOnError(err)
}
}
我的解决方法是要求接口的实现者向gob注册其类型.但是我不喜欢这样向调用者显示我的序列化选择.
My work-around would be to require implementers of the interface to register their type with gob. But I don't like how that reveals my serialization choices to the callers.
有没有避免这种情况的前进路线?
Is there any route forward that avoids this?
哲学论证
encoding/gob
程序包不能(或者不应该)对它自己的.由于gob
包创建了独立于应用程序或与应用程序分离的序列化形式,因此无法保证解码器中将存在接口类型的值;即使它们确实做到了(与具体的类型名称匹配),也不能保证它们代表相同的类型(或给定类型的相同实现).
Philosophical argumentation
The encoding/gob
package cannot (or rather should not) make that decision on its own. Since the gob
package creates a serialized form independent of / detached from the app, there is no guarantee that values of interface types will exist in the decoder; and even if they do (matched by the concrete type name), there is no guarantee that they represent the same type (or the same implementation of a given type).
通过调用 gob.Register()
(或
By calling gob.Register()
(or gob.RegisterName()
) you make that intent clear, you give green light to the gob
package to use that type. This also ensures that the type does exist, else you would not be able to pass a value of it when registering.
There's also a technical point of view that dictates this requirement (that you must register prior): you cannot obtain the reflect.Type
type descriptor of a type given by its string
name. Not just you, the encoding/gob
package can't do it either.
因此,通过要求您先调用gob.Register()
,gob
程序包将收到一个有问题的类型的值,因此它可以(并且将)在内部访问和存储其reflect.Type
描述符,因此当检测到此类型的值时,它便能够创建此类型的新值(例如,使用 reflect.New()
),以便将要解码的值存储到其中.
So by requiring you to call gob.Register()
prior, the gob
package will receive a value of the type in question, and therefore it can (and it will) access and store its reflect.Type
descriptor internally, and so when a value of this type is detected, it is capable of creating a new value of this type (e.g. using reflect.New()
) in order to store the value being decoded into it.
您不能按名称查找"类型的原因是,除非您明确引用它们,否则它们可能不会出现在二进制文件中(它们可能会被优化").有关详细信息,请参见以特殊方式调用所有函数Golang的前缀或后缀;和拆分客户端/服务器代码.注册自定义类型时(通过传递它们的值),您将对它们进行显式引用,从而确保它们不会从二进制文件中排除.
The reason why you can't "lookup" types by name is that they may not end up in your binary (they may get "optimized out") unless you explicitly refer to them. For details see Call all functions with special prefix or suffix in Golang; and Splitting client/server code. When registering your custom types (by passing values of them), you are making an explicit reference to them and thus ensuring that they won't get excluded from the binaries.