go - 类型未知时进行反序列化

标签 go serialization

我正在编写一个包,使用特定的传输方式在服务之间发送消息。

我希望程序包不了解所发送消息的类型。
我的第一个想法是将消息对象序列化为json,进行发送,在接收端反序列化,然后将go对象(作为interface{})传递给订阅代码。

序列化不是问题,但是我不知道通用包代码如何反序列化消息,因为它不知道类型。我想到了使用反射TypeOf(),并将该值作为消息的一部分传递。但是我看不到如何完成此操作,因为Type是一个接口,并且不导出实现的rtype。

如果接收方的应用程序收到interface{},则无论如何都要检查类型,所以也许它应该进行反序列化。还是接收方可以提供反射类型,以便包装可以反序列化?

或者它可以给接收者一个字段名到值的映射,但是我更喜欢实际的类型。

有什么建议么?

让我添加一个示例:

我有一个go通道,用于发送不同类型的对象的更改通知。由于go不支持标记的联合,因此将渠道类型定义为:

type UpdateInfo struct {
    UpdateType UpdateType
    OldObject interface{}
    NewObject interface{}
}

通道的接收端获取一个UpdateInfo,其中的OldObject和NewObject是发送的实际具体对象类型。

我想将其扩展到在应用程序之间工作,其中传输将通过消息队列来支持发布/订阅,多个使用者等。

最佳答案

TL; DR

只需使用json.Unmarshal即可。您可以使用传输工具将其轻巧地包装起来,然后在预先构建的JSON字节和调用者的json.Unmarshal参数上调用json.Decoder(或使用d.Decode实例,使用v interface{})。

更长一些,例如

考虑一下 json.Unmarshal 如何发挥自己的魔力。它的第一个参数是JSON(data []byte),但是它的第二个参数是interface{}类型:

func Unmarshal(data []byte, v interface{}) error

正如文档中所说的,v是否真的只是interface{}:

要将JSON解组为接口值,Unmarshal将其中之一存储在接口值中:

bool, for JSON booleans
float64, for JSON numbers
string, for JSON strings
[]interface{}, for JSON arrays
map[string]interface{}, for JSON objects
nil for JSON null

但是,如果v具有基础的具体类型,例如type myData struct { ... },它会更有趣。仅当v的基础类型为interface{}时,才执行上述操作。

它的actual implementation特别复杂,因为它经过了优化,可以同时进行反JSON化和分配给目标对象。但是,原则上,它主要是接口值的基础(具体)类型上的大型类型切换。

同时,您在问题中所描述的是,您将首先反序列化为通用JSON(这实际上意味着interface{}类型的变量),然后将您自己从预解码的JSON中分配给另一个类型为interface{}的变量,其中您自己的解码器的类型签名为:

func xxxDecoder(/* maybe some args here, */ v interface{}) error {
    var predecoded interface{}

    // get some json bytes from somewhere into variable `data`
    err := json.Unmarshal(data, &predecoded)

    // now emulate json.Unmarshal by getting field names and assigning
    ... this is the hard part ...
}

然后您可以通过编写以下代码来调用此代码:

type myData struct {
    Field1 int    `xxx:"Field1"`
    Field2 string `xxx:"Field2"`
}

因此,您知道JSON对象键“Field1”应使用整数填充您的Field1字段,而JSON对象键“Field2”应使用字符串填充您的Field2字段:

func whatever() {
    var x myData
    err := xxxDecode(..., &x)
    if err != nil { ... handle error ... }
    ... use x.Field1 and x.Field2 ...
}

但是,这很愚蠢。您可以这样写:

type myData struct {
    Field1 int    `json:"Field1"`
    Field2 string `json:"Field2"`
}

(或者甚至省略标签,因为字段的名称是默认的json标签),然后执行以下操作:

func xxxDecode(..., v interface{}) error {
    ... get data bytes as before ...
    return json.Unmarshal(data, v)
}

换句话说,只是让json.Unmarshal通过在相关数据结构中提供json标签来完成的所有工作。您仍然可以从json.Marshaljson.Unmarshal获得并通过特殊传输传输JSON数据字节。您进行发送和接收。 json.Marshaljson.Unmarshal可以完成所有艰苦的工作:您不必碰它!

看看Json.Unmarshal的工作原理仍然很有趣

跳至around line 660 of encoding/json/decode.go ,您将在其中找到处理JSON“对象”的事物({后跟}或代表键的字符串),例如:

func (d *decodeState) object(v reflect.Value) error {

有一些机制可以处理特殊情况(包括v可能不可设置和/或可能是应该遵循的指针的事实),然后确保vmap[T1]T2struct,如果是映射,这是合适的-解码对象中的“key”:value项时T1T2都可以工作。

如果一切顺利,它将进入从720行开始的JSON键和值扫描循环(for {,它将酌情中断或返回)。在此循环的每次行程中,代码首先读取JSON密钥,而:和value部分留待以后使用。

如果我们要解码为struct,那么解码器现在将使用结构的字段(名称和json:"..."标记)找到我们将用于存储在字段中的reflect.Value。1这是subv,通过调用右侧的v.Field(i)来找到i,有些复杂的操作来处理嵌入式匿名structs和指针跟随。不过,其核心只是subv = v.Field(i),其中i是该键在结构中的哪个字段。因此,subv现在是代表实际结构实例的值的reflect.Value,一旦我们解码了JSON键值对的值部分,就应该设置该值。

如果我们要解码为 map ,则首先将值解码为临时值,然后在解码后将其存储到 map 中。最好与struct-field存储共享它,但是我们需要一个不同的reflect函数将其存储到 map 中: v.SetMapIndex ,其中v是 map 的reflect.Value。这就是为什么对于 map 而言,subv指向临时Elem的原因。

现在我们准备将实际值转换为目标类型,因此我们返回JSON字节并使用冒号:字符并读取JSON值。我们获取值并将其存储到我们的存储位置(subv)。这是从第809行开始的代码(if destring {)。实际的分配是通过解码器功能(在第908行的d.literalStore或在第412行的d.value)完成的,而这些函数实际上在存储时对JSON值进行了解码。请注意,只有d.literalStore才真正存储该值-必要时,d.value调用d.arrayd.objectd.literalStore来递归地完成工作。

因此,d.literalStore包含许多switch v.Kind():它解析nulltruefalse或整数,字符串或数组,然后确保可以将结果值存储到v.Kind()中,并选择如何基于以下内容将结果值存储到v.Kind()中刚解码的内容与实际v.Kind()的组合。因此,这里有一些组合爆炸,但是可以完成工作。

如果所有方法均奏效,并且我们正在解码到 map ,则现在可能需要调整临时类型,找到实键,然后将转换后的值存储到 map 中。这就是通过867的最后一个大括号显示的第830行(if v.Kind() == reflect.Map {)。

1要查找字段,我们首先查看encoding/json/encode.go以查找cachedTypeFields。它是 typeFields 的缓存版本。这是找到json标签并将其放入 slice 的地方。结果通过cachedTypeFields缓存在由struct类型的反射类型值索引的映射中。因此,我们得到的是第一次使用struct类型进行的缓慢查找,然后进行快速查找,以获取有关如何进行解码的信息。该信息 slice 从json-tag-or-field名称映射到:field;类型;它是否是匿名结构的子字段;依此类推:在编码方面,我们需要知道的所有信息才能正确对其进行解码或对其进行编码。 (我并没有仔细查看这段代码。)

关于go - 类型未知时进行反序列化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59062330/

相关文章:

performance - 如何加速 Google App Engine Go 单元测试?

go - 提供动态长度响应数据

go - k8s 中 secret 列表的 LabelSelector

java - 序列化 LazyLoaded Hibernate 表导致异常

java.io.NotSerializedException : sun. nio.fs.WindowsPath - 如何修复它?

go - 如果停止,如何自动重启 go web 服务器

go - 如何 append 到二维 slice

c++ - boost 串行器 : getting warning C4308 unless using virtual class destructor

python - Flask - 直接在 session 中存储对象

c# - 如何在 C# 中自定义反序列化包含自己字段的对象