Golang?json?庫中的RawMessage功能原理
正文
json 作為一種通用的編解碼協(xié)議,可閱讀性上比 thrift,protobuf 等協(xié)議要好一些,同時編碼的 size 也會比 xml 這類協(xié)議要小,在市面上用的非常多。甚至在很多業(yè)務上,我們的線上實例消耗最大的部分就是 json 的序列化和反序列化。這也是為什么很多 Gopher 會致力于研究怎樣最有效地優(yōu)化這個過程。
今天我們來學習一個 Golang 官方 json 庫提供了一個經(jīng)典能力:RawMessage。
什么是序列化
首先我們思考一下所謂序列化指的是什么呢?
參考 json 包中 Marshaler 和 Unmarshaler 兩個接口定義:
// Marshaler is the interface implemented by types that // can marshal themselves into valid JSON. type Marshaler interface { ?? ?MarshalJSON() ([]byte, error) } 序列化,也就是 Marshal,需要將一種類型轉(zhuǎn)換為一個字節(jié)數(shù)組,也就是這里接口返回值的 []byte。 go復制代碼// Unmarshaler is the interface implemented by types // that can unmarshal a JSON description of themselves. // The input can be assumed to be a valid encoding of // a JSON value. UnmarshalJSON must copy the JSON data // if it wishes to retain the data after returning. // // By convention, to approximate the behavior of Unmarshal itself, // Unmarshalers implement UnmarshalJSON([]byte("null")) as a no-op. type Unmarshaler interface { ?? ?UnmarshalJSON([]byte) error }
而反序列化,則是序列化的逆過程,接收一個字節(jié)數(shù)組,轉(zhuǎn)換為目標的類型值。
事實上如果你對自定義的類型實現(xiàn)了上面兩個接口,調(diào)用 json 包的 json.Marshal 以及 json.Unmarshal 函數(shù)時就會執(zhí)行你的實現(xiàn)。
簡言之,本質(zhì)上看,序列化就是將一個 object 轉(zhuǎn)換為字節(jié)數(shù)組,即 []byte 的過程。
RawMessage
RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding.
RawMessage 具體來講是 json 庫中定義的一個類型。它實現(xiàn)了 Marshaler 接口以及 Unmarshaler 接口,以此來支持序列化的能力。注意上面我們引用 官方 doc 的說明。我們直接來看看源碼中的實現(xiàn):
// RawMessage is a raw encoded JSON value. // It implements Marshaler and Unmarshaler and can // be used to delay JSON decoding or precompute a JSON encoding. type RawMessage []byte // MarshalJSON returns m as the JSON encoding of m. func (m RawMessage) MarshalJSON() ([]byte, error) { ?? ?if m == nil { ?? ??? ?return []byte("null"), nil ?? ?} ?? ?return m, nil } // UnmarshalJSON sets *m to a copy of data. func (m *RawMessage) UnmarshalJSON(data []byte) error { ?? ?if m == nil { ?? ??? ?return errors.New("json.RawMessage: UnmarshalJSON on nil pointer") ?? ?} ?? ?*m = append((*m)[0:0], data...) ?? ?return nil } var _ Marshaler = (*RawMessage)(nil) var _ Unmarshaler = (*RawMessage)(nil)
非常直接,其實 RawMessage 底層就是一個 []byte。序列化時是直接把自己 return 回去了。而反序列化時則是把入?yún)⒌?[]byte 拷貝一份,寫入自己的內(nèi)存地址即可。
有意思了,前一節(jié)我們提到過,序列化后產(chǎn)出的本來就是一個 []byte,那為什么還要專門再搞一個 RawMessage 出來,有什么作用呢?
沒錯,RawMessage 其實人如其名,代表的就是一個終態(tài)。什么意思呢?我本來就是個字節(jié)數(shù)組,那么如果你要對我進行序列化,就不需要什么成本,直接把我這個字節(jié)數(shù)組拿過去即可。如果要反序列化,沒事,你直接把原來的字節(jié)數(shù)組拿到就夠了。
這就是 Raw 的含義,原來是什么樣,現(xiàn)在就是什么樣。原樣拿過來即可。
這里參照 Using Go’s json.RawMessage 的經(jīng)典解釋。
We can think of the raw message as a piece of information that we decide to ignore at the moment. The information is still there but we choose to keep it in its raw form — a byte array.
我們可以把 RawMessage 看作是一部分可以暫時忽略的信息,以后可以進一步去解析,但此時不用。所以,我們保留它的原始形式,還是個字節(jié)數(shù)組即可。
使用場景
軟件開發(fā)中,我們經(jīng)常說不要過度設計,好的代碼應當有明確的使用場景,而且能高效地解決一類問題,而不是在設想和概念上造出來一個未經(jīng)過驗證的空中樓閣。
那么 RawMessage 是不是這樣一個空中樓閣呢?其實并不是。
我們可以將其當做一個【占位符】。設想一下,我們給某種業(yè)務場景定義了一個通用的 model,其中部分數(shù)據(jù)需要在不同場景下對應不同的結(jié)構(gòu)體。這個時候怎么 Marshal 成字節(jié)數(shù)組,存入數(shù)據(jù)庫,以及讀出數(shù)據(jù),還原出 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) }
這里 c 是我們臨時定義的結(jié)構(gòu)體,body 是明確的一個字符串,而 header 是可變的。
還記得么?RawMessage 本質(zhì)是個 []byte,所以我們可以用
json.RawMessage(`{"precomputed": true}`)
來將一個字符串轉(zhuǎn)換為 RawMessage。隨后對其進行 Marshal,輸出的結(jié)果如下:
{
"header": {
"precomputed": true
},
"body": "Hello Gophers!"
}
發(fā)現(xiàn)了么?
這里 "precomputed": true 跟我們構(gòu)造的 RawMessage 是一模一樣的,所以對應到第一個能力:在序列化時使用一個預先計算好的 json 值。
延遲解析 json 結(jié)構(gòu)
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 可能存在兩種結(jié)構(gòu)描述,一種是 RGB,另一種是 YCbCr,而我們對應到底層存儲,又希望能復用,這是非常常見的。
所以,這里采用了【兩級反序列化】的策略:
第一級,解析出來公共字段,利用 json.RawMessage 延遲這部分差異字段的解析。
第二級,根據(jù)已經(jīng)解析出來的字段(一般是有類似 type 的語義),判斷再次反序列化時要使用的結(jié)構(gòu),基于 json.RawMessage 再次 Unmarshal,拿到最終的數(shù)據(jù)。
上面的示例輸出結(jié)果如下:
YCbCr &{255 0 -10}
RGB &{98 218 255}
總結(jié)
json 提供的 RawMessage 是直接暴露了底層的 []byte 作為交互憑證,它可以被內(nèi)嵌在各種結(jié)構(gòu)體中。作為不可變的字段類型的 placeholder,延遲解析。相較于 string 類型效率更高。從實現(xiàn)上看非常簡單,只是封裝了一層字節(jié)數(shù)組的交互,大家可以放心使用。
以上就是Golang json 庫中的RawMessage功能原理的詳細內(nèi)容,更多關(guān)于Golang json庫RawMessage的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Go中各種newreader和newbuffer的使用總結(jié)
這篇文章主要為大家詳細介紹了Go語言中各種newreader和newbuffer的使用的相關(guān)資料,文中的示例代碼講解詳細,具有一定的參考價值,感興趣的小伙伴可以了解下2023-11-11