我正在编写一个包,使用特定的传输方式在服务之间发送消息。
我希望程序包不了解所发送消息的类型。
我的第一个想法是将消息对象序列化为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.Marshal
和json.Unmarshal
获得并通过特殊传输传输JSON数据字节。您进行发送和接收。 json.Marshal
和json.Unmarshal
可以完成所有艰苦的工作:您不必碰它!看看
Json.Unmarshal
的工作原理仍然很有趣跳至around line 660 of
encoding/json/decode.go
,您将在其中找到处理JSON“对象”的事物({
后跟}
或代表键的字符串),例如:func (d *decodeState) object(v reflect.Value) error {
有一些机制可以处理特殊情况(包括
v
可能不可设置和/或可能是应该遵循的指针的事实),然后确保v
是map[T1]T2
或struct
,如果是映射,这是合适的-解码对象中的“key”:value项时T1
和T2
都可以工作。如果一切顺利,它将进入从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.array
,d.object
或d.literalStore
来递归地完成工作。因此,
d.literalStore
包含许多switch v.Kind()
:它解析null
或true
或false
或整数,字符串或数组,然后确保可以将结果值存储到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/