RawMessage 具体来讲是 json 库中定义的一个类型。它实现了 Marshaler 接口以及 Unmarshaler 接口,以此来支持序列化的能力。注意上面我们引用 官方 doc 的说明。
使用场景
设想一下,我们给某种业务场景定义了一个通用的 model,其中部分数据需要在不同场景下对应不同的结构体。这个时候怎么 Marshal 成字节数组,存入数据库,以及读出数据,还原出 model 呢?
我们就可以将这个可变的字段定义为 json.RawMessage
,利用它适配万物的能力来进行读写。
复用预计算的 json 值
package main
import (
"encoding/json"
"fmt"
"os"
)
func main() {
h := json.RawMessage(`{"precomputed": true}`)
c := struct {
Header *json.RawMessage `json:"header"`
Body string `json:"body"`
}{Header: &h, Body: "Hello Gophers!"}
b, err := json.MarshalIndent(&c, "", "\t")
if err != nil {
fmt.Println("error:", err)
}
os.Stdout.Write(b)
}
输出:
{
"header": {
"precomputed": true
},
"body": "Hello Gophers!"
}
这里 "precomputed": true
跟我们构造的 RawMessage 是一模一样的,所以对应到第一个能力:在序列化时使用一个预先计算好的 json 值。
延迟解析 json 结构
package main
import (
"encoding/json"
"fmt"
"log"
)
func main() {
type Color struct {
Space string
Point json.RawMessage // delay parsing until we know the color space
}
type RGB struct {
R uint8
G uint8
B uint8
}
type YCbCr struct {
Y uint8
Cb int8
Cr int8
}
var j = []byte(`[
{"Space": "YCbCr", "Point": {"Y": 255, "Cb": 0, "Cr": -10}},
{"Space": "RGB", "Point": {"R": 98, "G": 218, "B": 255}}
]`)
var colors []Color
err := json.Unmarshal(j, &colors)
if err != nil {
log.Fatalln("error:", err)
}
for _, c := range colors {
var dst any
switch c.Space {
case "RGB":
dst = new(RGB)
case "YCbCr":
dst = new(YCbCr)
}
err := json.Unmarshal(c.Point, dst)
if err != nil {
log.Fatalln("error:", err)
}
fmt.Println(c.Space, dst)
}
}
这里的例子其实更典型。Color 中的 Point 可能存在两种结构描述,一种是 RGB,另一种是 YCbCr,而我们对应到底层存储,又希望能复用,这是非常常见的。
所以,这里采用了【两级反序列化】的策略:
- 第一级,解析出来公共字段,利用 json.RawMessage 延迟这部分差异字段的解析。
- 第二级,根据已经解析出来的字段(一般是有类似 type 的语义),判断再次反序列化时要使用的结构,基于 json.RawMessage 再次 Unmarshal,拿到最终的数据。
上面的示例输出结果如下:
YCbCr &{255 0 -10}
RGB &{98 218 255}
使用示例:
1)如果json串没有固定的格式导致不好定义与其相对应的结构体时,我们可以使用json.RawMessage
原始字节数据保存下来 ,然后一层一层的进行解析:
type sendMsg struct {
Name string `json:"name"`
Info string `json:"info"`
}
func jsonTest() {
jsonStr := `{"sendMsg":{"name":"louiswang","info":"这里是一条消息"},"say":"Hello"}`
var data map[string]json.RawMessage
if err := json.Unmarshal([]byte(jsonStr), &data); err != nil {
fmt.Println("json 解析失败了:", err)
return
}
var saystr string
if err := json.Unmarshal(data["say"], &saystr); err != nil {
fmt.Println("RawMessage 解析失败了:", err)
return
}
fmt.Printf("%#v-------%T\n", saystr, saystr)
var resMsg sendMsg
if err := json.Unmarshal(data["sendMsg"], &resMsg); err != nil {
fmt.Println("RawMessage 解析失败了(sendMsg):", err)
return
}
fmt.Printf("%#v-----%T\n", resMsg, resMsg)
fmt.Println(resMsg.Name, resMsg.Info)
}
总结
json 提供的 RawMessage 是直接暴露了底层的 []byte 作为交互凭证,它可以被内嵌在各种结构体中。作为不可变的字段类型的 placeholder,延迟解析。相较于 string 类型效率更高。从实现上看非常简单,只是封装了一层字节数组的交互,大家可以放心使用。