包装gob解码器

huangapple go评论76阅读模式
英文:

Wrapping gob decoder

问题

我正在存储加密的gob数据在一个键值数据库中,并希望在一个包中有一个方便的函数,该函数接受一个键和一个接口作为参数,它解密存储的值,将其解码为传递的接口,并且使用者的代码可以继续运行而不需要知道存储的数据的任何信息。

我无论如何都无法弄清楚为什么使用gob时无法写入指针值,而使用json编码器却可以。简化后的代码如下所示:

type Foo struct {
	A string
	B string
}

func marshal(i interface{}) ([]byte, error) {
	var indexBuffer bytes.Buffer
	encoder := gob.NewEncoder(&indexBuffer)
	err := encoder.Encode(&i)
	return indexBuffer.Bytes(), err
}

func unmarshal(data []byte, e interface{}) error {
	buf := bytes.NewBuffer(data)
	dec := gob.NewDecoder(buf)
	err := dec.Decode(&e)
	fmt.Println("Unmarshal", e)
	return err
}

func marshalJ(i interface{}) ([]byte, error) {
	return json.Marshal(i)
}

func unmarshalJ(data []byte, e interface{}) error {
	return json.Unmarshal(data, e)
}

func main() {
	foo := Foo{"Hello", "world!"}
	gob.Register(Foo{})
	data, err := marshal(foo)
	fmt.Println("got", len(data), err)
	var bar Foo
	err = unmarshal(data, &bar)
	fmt.Println("Main err", err)
	fmt.Println("Main", bar)
	fmt.Println("-------------------------")
	data, err = marshalJ(foo)
	fmt.Println("got J", len(data), err)
	err = unmarshalJ(data, &bar)
	fmt.Println("Main J err", err)
	fmt.Println("Main J", bar)
}

(play链接)

unmarshalJ的行为符合我的预期,在Main函数中,我的bar变量是foo的副本,因为unmarshalJ接收到了bar的指针并将其传递给了Decode函数。

另一方面,unmarshal函数在本地改变了参数,但是这个改变并没有反映到Main函数中的bar变量上。在内部,gob.Decode函数传递了reflect.ValueOf的值,而我一直试图避免使用reflect,所以我并不真正理解它的作用。

我的两个问题是:

  1. 是否可能像我想的那样有一个包装函数,并且使调用者的值像json编码器那样被改变?
  2. 更像是一个额外的问题,因为这让我觉得我对Go语言中的指针还没有完全掌握:如何改变unmarshal.e的值,但是Main.bar的值却没有改变,尽管unmarshal.e是指向Main.bar的指针?在这里让我困惑的另一件事是,在unmarshal函数内部,我必须将&e传递给Decode函数,否则会出现want struct type main.Foo; got non-struct的错误。
英文:

I'm storing encrypted gobs in a k-v database and was hoping to have a convenience function in a package which takes a key and an interface, it decrypts the stored value, decodes it into the passed interface and the consumer code can hum along without having to know anything about the data being stored.

I can't for the world figure out why I fail to write to the pointer value using gob, when it works with the json encoder. Dumbed-down the code looks like this:

type Foo struct {
	A string
	B string
}

func marshal(i interface{}) ([]byte, error) {
	var indexBuffer bytes.Buffer
	encoder := gob.NewEncoder(&indexBuffer)
	err := encoder.Encode(&i)
	return indexBuffer.Bytes(), err
}

func unmarshal(data []byte, e interface{}) error {
	buf := bytes.NewBuffer(data)
	dec := gob.NewDecoder(buf)
	err := dec.Decode(&e)
	fmt.Println("Unmarshal", e)
	return err
}

func marshalJ(i interface{}) ([]byte, error) {
	return json.Marshal(i)
}

func unmarshalJ(data []byte, e interface{}) error {
	return json.Unmarshal(data, e)
}

func main() {
	foo := Foo{"Hello", "world!"}
	gob.Register(Foo{})
	data, err := marshal(foo)
	fmt.Println("got", len(data), err)
	var bar Foo
	err = unmarshal(data, &bar)
	fmt.Println("Main err", err)
	fmt.Println("Main", bar)
	fmt.Println("-------------------------")
	data, err = marshalJ(foo)
	fmt.Println("got J", len(data), err)
	err = unmarshalJ(data, &bar)
	fmt.Println("Main J err", err)
	fmt.Println("Main J", bar)
}

(play link)

The unmarshalJ behaves like I expect, that is in Main my bar variable is a copy of foo since unmarshalJ receives a pointer to bar and passes it on.

The unmarshal function on the other hand have the parameter changed locally, but the change isn't made to the bar in Main. Internally gob.Decode passes on reflect.ValueOf, and I have always tried to avoid using reflect so I don't really understand what that does.

My two questions would be:

  1. Is it possible to have a wrapper like I want and have the caller's value changed like it works with the json encoder?
  2. More of a bonus question because this makes me feel I'm not fully grasping pointers in go: How can the value of unmarshal.e be changed, but Main.bar not be changed when unmarshal.e is a pointer to Main.bar? Another thing which confuse me here is that inside unmarshal I have to pass &e to Decode or it fails with want struct type main.Foo; got non-struct.

答案1

得分: 0

有两个问题,第一个问题是你给unmarshal函数传递了一个指针,然后在unmarshal函数内部又创建了另一个指针,所以最终你传递给gob解码器的是**Foo

第二个问题是你在函数内部获取了interface{}类型的指针。这会影响数据的编码方式。如果你将指针传递给函数并且不修改函数内部的变量,则一切正常。

修复后的代码如下所示,playground链接

type Foo struct {
	A string
	B string
}

func marshal(i interface{}) ([]byte, error) {
	var indexBuffer bytes.Buffer
	encoder := gob.NewEncoder(&indexBuffer)
	err := encoder.Encode(i)
	return indexBuffer.Bytes(), err
}

func unmarshal(data []byte, e interface{}) error {
	buf := bytes.NewBuffer(data)
	dec := gob.NewDecoder(buf)
	err := dec.Decode(e)
	fmt.Println("Unmarshal", e)
	return err
}

func marshalJ(i interface{}) ([]byte, error) {
	return json.Marshal(i)
}

func unmarshalJ(data []byte, e interface{}) error {
	return json.Unmarshal(data, e)
}

func main() {
	foo := Foo{"Hello", "world!"}
	gob.Register(Foo{})
	data, err := marshal(&foo)
	fmt.Println("got", len(data), err)
	var bar Foo
	err = unmarshal(data, &bar)
	fmt.Println("Main err", err)
	fmt.Println("Main", bar)
	fmt.Println("-------------------------")
	data, err = marshalJ(foo)
	fmt.Println("got J", len(data), err)
	err = unmarshalJ(data, &bar)
	fmt.Println("Main J err", err)
	fmt.Println("Main J", bar)
}

编辑:回应评论。

防止这种问题有时很困难,我认为问题的根源是使用了interface{},它丢弃了类型信息,除了为每种类型创建一个显式的解码器函数之外,我们无法做任何事情来解决这个问题。第二个“问题”是gob在类型不匹配时会忽略这个事实而不报错,因此无法提供任何关于我们做错了什么的指示。

在解码方面,我们可以添加更严格的类型检查。我们可以要求解码器将解码后的值放入一个interface{}中,然后检查解码后的类型是否与e的类型匹配:

type Foo struct {
	A string
	B string
}

func marshal(i interface{}) ([]byte, error) {
	var indexBuffer bytes.Buffer
	encoder := gob.NewEncoder(&indexBuffer)
	err := encoder.Encode(&i)
	return indexBuffer.Bytes(), err
}

func unmarshal(data []byte, e interface{}) (err error) {
	buf := bytes.NewBuffer(data)
	dec := gob.NewDecoder(buf)

	eVal := reflect.ValueOf(e)
	eType := eVal.Type()
	if eVal.Kind() != reflect.Ptr {
		return errors.New("e must be a pointer")
	}

	var u interface{}
	err = dec.Decode(&u)
	uVal := reflect.ValueOf(u)
	uType := uVal.Type()
	if eType.Elem() != uType {
		return fmt.Errorf("decoded type '%s' and underlying type of e '%s' not the same", uType.String(), eType.Elem())
	}

	eVal.Elem().Set(uVal)

	return err
}

func main() {
	foo := Foo{"Hello", "world!"}
	gob.Register(Foo{})
	data, err := marshal(foo)
	fmt.Println("got", len(data), err)
	var bar Foo
	var invalid interface{} = bar
	err = unmarshal(data, &invalid)
	fmt.Println("Main err", err)
	fmt.Println("Main", bar)
	err = unmarshal(data, &bar)
	fmt.Println("Main err", err)
	fmt.Println("Main", bar)
}

输出:

got 61 <nil>
Main err decoded type 'main.Foo' and underlying type of e 'interface {}' not the same
Main { }
Main err <nil>
Main {Hello world!}
英文:

There are two issues, 1st is that you give unmarshal a pointer, and then make another pointer inside unmarshal itself, so you end up passing **Foo to the gob decoder.

The second issue is that you take pointers of the interface{}'es inside the function. This somehow effects how the data is encoded. Everything works if you pass in pointers to the functions and don't modify the variables inside the functions.

The fixed code looks like this, playground link:

type Foo struct {
	A string
	B string
}

func marshal(i interface{}) ([]byte, error) {
	var indexBuffer bytes.Buffer
	encoder := gob.NewEncoder(&amp;indexBuffer)
	err := encoder.Encode(i)
	return indexBuffer.Bytes(), err
}

func unmarshal(data []byte, e interface{}) error {
	buf := bytes.NewBuffer(data)
	dec := gob.NewDecoder(buf)
	err := dec.Decode(e)
	fmt.Println(&quot;Unmarshal&quot;, e)
	return err
}

func marshalJ(i interface{}) ([]byte, error) {
	return json.Marshal(i)
}

func unmarshalJ(data []byte, e interface{}) error {
	return json.Unmarshal(data, e)
}

func main() {
	foo := Foo{&quot;Hello&quot;, &quot;world!&quot;}
	gob.Register(Foo{})
	data, err := marshal(&amp;foo)
	fmt.Println(&quot;got&quot;, len(data), err)
	var bar Foo
	err = unmarshal(data, &amp;bar)
	fmt.Println(&quot;Main err&quot;, err)
	fmt.Println(&quot;Main&quot;, bar)
	fmt.Println(&quot;-------------------------&quot;)
	data, err = marshalJ(foo)
	fmt.Println(&quot;got J&quot;, len(data), err)
	err = unmarshalJ(data, &amp;bar)
	fmt.Println(&quot;Main J err&quot;, err)
	fmt.Println(&quot;Main J&quot;, bar)
}

Edit: as response to the comment.

Preventing issues like this is sometimes hard, the root of the issue in my opinion is the use of interface{} which throws away type info, nothing we can do about this unfortunately(other than making an explicit decoder function for each type). The second "issue" is that gob just ignores the fact that types mismatch without error, thus not giving us any indication about what we are doing wrong.

What we can do on the decoding side is to add more strict type checking. We can ask the decoder to just put the decoded value in a interface{} and then check if the decoded type matches the type of e:


type Foo struct {
	A string
	B string
}

func marshal(i interface{}) ([]byte, error) {
	var indexBuffer bytes.Buffer
	encoder := gob.NewEncoder(&amp;indexBuffer)
	err := encoder.Encode(&amp;i)
	return indexBuffer.Bytes(), err
}

func unmarshal(data []byte, e interface{}) (err error) {
	buf := bytes.NewBuffer(data)
	dec := gob.NewDecoder(buf)

	eVal := reflect.ValueOf(e)
	eType := eVal.Type()
	if eVal.Kind() != reflect.Ptr {
		return errors.New(&quot;e must be a pointer&quot;)
	}

	var u interface{}
	err = dec.Decode(&amp;u)
	uVal := reflect.ValueOf(u)
	uType := uVal.Type()
	if eType.Elem() != uType {
		return fmt.Errorf(&quot;decoded type &#39;%s&#39; and underlying type of e &#39;%s&#39; not the same&quot;, uType.String(), eType.Elem())
	}

	eVal.Elem().Set(uVal)

	return err
}

func main() {
	foo := Foo{&quot;Hello&quot;, &quot;world!&quot;}
	gob.Register(Foo{})
	data, err := marshal(foo)
	fmt.Println(&quot;got&quot;, len(data), err)
	var bar Foo
	var invalid interface{} = bar
	err = unmarshal(data, &amp;invalid)
	fmt.Println(&quot;Main err&quot;, err)
	fmt.Println(&quot;Main&quot;, bar)
	err = unmarshal(data, &amp;bar)
	fmt.Println(&quot;Main err&quot;, err)
	fmt.Println(&quot;Main&quot;, bar)
}

Outputs:

got 61 &lt;nil&gt;
Main err decoded type &#39;main.Foo&#39; and underlying type of e &#39;interface {}&#39; not the same
Main { }
Main err &lt;nil&gt;
Main {Hello world!}

huangapple
  • 本文由 发表于 2022年1月2日 17:58:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/70555069.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定