首页 > 解决方案 > 如何在go中保存和加载接口结构

问题描述

来自 python,我习惯于用自定义方法编写一个类。启动它,保存对象并再次加载它。我正在尝试在 go 中完成类似的事情。在阅读了关于序列化的文章后,我尝试使用 marshall/unmarshall 方法。

但是下面的代码 deos 不起作用,因为它导致 method.Method = nil。我希望能够调用 method.Method.Calculcate()

下面是我的尝试。

import (
    "encoding/json"
    "fmt"
    "log"
)

type Calculator struct {
    Method Method `json:"Method"`
}

type method1 struct {
    Input string `json:"input"`
}

func NewMethod1(input string) Method {
    return &method1{
        Input: input,
    }
}

type method2 struct {
    Input string `json:"input"`
}

func NewMethod2(input string) Method {
    return &method2{
        Input: input,
    }
}

type Method interface {
    Calculcate()
}

func (m *method1) Calculcate() {
}
func (m *method2) Calculcate() {
    }

func main() {
    model := Calculator{
        Method: NewMethod1("inputData"),
    }
    model.Method.Calculcate()
    var jsonData []byte
    jsonData, err := json.Marshal(model)
    if err != nil {
        log.Println(err)
    }
    fmt.Println(string(jsonData))

    var method Calculator
    json.Unmarshal(jsonData, &method)
    if err != nil {
        log.Println(err)
    }
}

我想加载结构并在计算时使用相同的代码来启动方法 1 和方法 2。这意味着我事先不知道我是在加载方法 1 还是方法 2。下面是一点点伪代码,解释了我想要什么。

Calculator :=Load Calculator()
model := Calculator{
    Method: NewMethod1("inputData"),
}
model.Method.Calculcate()

标签: goencodinginterfacedecoding

解决方案


问题是当字段具有接口类型(多种类型可能实现一个接口)时,解码 JSON 不知道使用什么具体类型。在您的示例中可能很明显,但请再想一想:您将此 JSON 发送到另一台计算机尝试对其进行解码。JSON 表示不记录接口值的具体类型(在本例中为*main.method1),因此另一端不知道要为接口字段实例化什么Calculator.Method

如果需要这种序列化和反序列化,请改用encoding/gob包。该包还写入类型信息,因此解码器部分至少知道类型名称encoding/gob不传输类型定义,因此如果另一部分具有不同版本的类型,该过程也可能失败。这记录在包文档中:

不检查接口类型的兼容性;为了传输,所有接口类型都被视为单个“接口”类型的成员,类似于 int 或 []byte - 实际上它们都被视为接口{}。接口值以字符串的形式传输,该字符串标识正在发送的具体类型(必须通过调用 Register 预先定义的名称),后跟后面数据长度的字节数(因此如果不能跳过,则可以跳过该值)存储),然后是存储在接口值中的具体(动态)值的通常编码。(nil 接口值由空字符串标识,不传输任何值。)解码器收到后验证解包的具体项是否满足接收变量的接口。

此外,为了使encoding/gob包能够根据其名称实例化类型,您必须注册这些类型,更具体地说是这些类型的值。

这是一个使用的工作示例encoding/gob

首先让我们改进Calculate()方法以查看它们被调用:

func (m *method1) Calculcate() {
    fmt.Println("method1.Calculate() called, input:", m.Input)
}

func (m *method2) Calculcate() {
    fmt.Println("method2.Calculate() called, input:", m.Input)
}

现在是序列化/反序列化过程:

// Register the values we use for the Method interface
gob.Register(&method1{})
gob.Register(&method2{})

model := Calculator{
    Method: NewMethod1("inputData"),
}
model.Method.Calculcate()

buf := &bytes.Buffer{}
enc := gob.NewEncoder(buf)
if err := enc.Encode(model); err != nil {
    log.Println(err)
    return
}
fmt.Println(buf.Bytes())

var model2 Calculator
dec := gob.NewDecoder(buf)
if err := dec.Decode(&model2); err != nil {
    log.Println(err)
    return
}
model2.Method.Calculcate()

这将输出(在Go Playground上尝试):

method1.Calculate() called, input: inputData
[35 255 129 3 1 1 10 67 97 108 99 117 108 97 116 111 114 1 255 130 0 1 1 1 6 77 101 116 104 111 100 1 16 0 0 0 48 255 130 1 13 42 109 97 105 110 46 109 101 116 104 111 100 49 255 131 3 1 1 7 109 101 116 104 111 100 49 1 255 132 0 1 1 1 5 73 110 112 117 116 1 12 0 0 0 16 255 132 12 1 9 105 110 112 117 116 68 97 116 97 0 0]
method1.Calculate() called, input: inputData

推荐阅读