【问题标题】:Wrapping gob decoder包装gob解码器
【发布时间】:2022-01-03 23:23:36
【问题描述】:

我将加密的 gob 存储在 k-v 数据库中,并希望在带有密钥和接口的包中具有便利功能,它解密存储的值,将其解码为传递的接口,并且消费者代码可以嗡嗡作响无需了解所存储数据的任何信息。

当它与 json 编码器一起工作时,我无法弄清楚为什么我无法使用 gob 写入指针值。简化后的代码如下所示:

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)

unmarshalJ 的行为与我预期的一样,即在 Main 中,我的 bar 变量是 foo 的副本,因为 unmarshalJ 收到指向 bar 的指针并将其传递。

另一方面,unmarshal 函数在本地更改了参数,但并未对 Main 中的栏进行更改。 gob.Decode 在内部传递了reflect.ValueOf,我一直试图避免使用reflect,所以我不太明白它的作用。

我的两个问题是:

  1. 是否有可能拥有我想要的包装器,并像使用 json 编码器一样更改调用者的值?
  2. 这是一个额外的问题,因为这让我觉得我没有完全掌握 go 中的指针:如何更改 unmarshal.e 的值,但当 unmarshal.e 是指向 Main 的指针时 Main.bar 不会更改。酒吧?让我感到困惑的另一件事是,在 unmarshal 内部,我必须将 &e 传递给 Decode,否则它会以 want struct type main.Foo; got non-struct 失败。

【问题讨论】:

标签: go gob


【解决方案1】:

有两个问题,第一个是你给unmarshal一个指针,然后在unmarshal本身内部创建另一个指针,所以你最终将**Foo传递给gob解码器。

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

固定的代码是这样的,playground link:

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!}

【讨论】:

  • 谢谢,错误从我做encoder.Encode(&amp;i) 开始,然后我尝试了不同的方法来解码错误编码的数据。让我感到困惑的是 e 在 unmarshal 中有一个值,我希望 bar 也有它,因为 e 是指向 bar 的指针。我的理解是,由于我提供了一个 **bar 进行编码,所以 gob 用一个新值覆盖了 *bar,从而覆盖了原始指针。这就是为什么 unmarshal 显示 e 的值而 main 没有显示 bar 的值?有什么好主意如何调试这种情况? IE如何理解e不再是传入的Foo指针?
  • @Jon 我在我的答案中添加了一个编辑,因为它超出了我的提交范围。希望这会有所帮助
猜你喜欢
  • 2016-01-09
  • 1970-01-01
  • 1970-01-01
  • 2012-12-16
  • 2021-08-04
  • 2016-09-16
  • 2021-06-14
  • 1970-01-01
  • 2021-05-10
相关资源
最近更新 更多