Golang json 库中的RawMessage功能原理
作者:ag9920 发布时间:2024-04-30 10:07:27
json 作为一种通用的编解码协议,可阅读性上比 thrift,protobuf 等协议要好一些,同时编码的 size 也会比 xml 这类协议要小,在市面上用的非常多。甚至在很多业务上,我们的线上实例消耗最大的部分就是 json 的序列化和反序列化。这也是为什么很多 Gopher 会致力于研究怎样最有效地优化这个过程。
今天我们来学习一个 Golang 官方 json 库提供了一个经典能力: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,需要将一种类型转换为一个字节数组,也就是这里接口返回值的 []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
}
而反序列化,则是序列化的逆过程,接收一个字节数组,转换为目标的类型值。
事实上如果你对自定义的类型实现了上面两个接口,调用 json 包的 json.Marshal 以及 json.Unmarshal 函数时就会执行你的实现。
简言之,本质上看,序列化就是将一个 object 转换为字节数组,即 []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 库中定义的一个类型。它实现了 Marshaler 接口以及 Unmarshaler 接口,以此来支持序列化的能力。注意上面我们引用 官方 doc 的说明。我们直接来看看源码中的实现:
// 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 回去了。而反序列化时则是把入参的 []byte 拷贝一份,写入自己的内存地址即可。
有意思了,前一节我们提到过,序列化后产出的本来就是一个 []byte,那为什么还要专门再搞一个 RawMessage 出来,有什么作用呢?
没错,RawMessage 其实人如其名,代表的就是一个终态。什么意思呢?我本来就是个字节数组,那么如果你要对我进行序列化,就不需要什么成本,直接把我这个字节数组拿过去即可。如果要反序列化,没事,你直接把原来的字节数组拿到就够了。
这就是 Raw 的含义,原来是什么样,现在就是什么样。原样拿过来即可。
这里参照 Using Go’s json.RawMessage 的经典解释。
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 看作是一部分可以暂时忽略的信息,以后可以进一步去解析,但此时不用。所以,我们保留它的原始形式,还是个字节数组即可。
使用场景
软件开发中,我们经常说不要过度设计,好的代码应当有明确的使用场景,而且能高效地解决一类问题,而不是在设想和概念上造出来一个未经过验证的空中楼阁。
那么 RawMessage 是不是这样一个空中楼阁呢?其实并不是。
我们可以将其当做一个【占位符】。设想一下,我们给某种业务场景定义了一个通用的 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)
}
这里 c 是我们临时定义的结构体,body 是明确的一个字符串,而 header 是可变的。
还记得么?RawMessage 本质是个 []byte,所以我们可以用
json.RawMessage(`{"precomputed": true}`)
来将一个字符串转换为 RawMessage。随后对其进行 Marshal,输出的结果如下:
{
"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}
来源:https://juejin.cn/post/7149848236017057805


猜你喜欢
- 简单Python词法分析器实现,供大家参考,具体内容如下词法分析器状态转换图:词法分析器总流程图:预处理程序:词法分析器:词法分析器程序详细
- 我们每天接触到各类应用,如社交、在线文档、直播等,后端都需要使用WebSocket技术提供实时通信能力。本文介绍如何使用Golang实现实时
- 尽管很多 NoSQL 数据库近几年大放异彩,但是像 MySQL 这样的关系型数据库依然是互联网的主流数据库之一,每个学 Python 的都有
- 使用MSSQL的站长朋友都会被MSSQL数据库吃内存的能力佩服得五体投地,一个小小的网站,运行若干天之后,MSSQL就会把服务器上所有的内存
- 背景最近在负责开发维护的一款数据平台,有一个功能是把数据从某个源头数据源(如常规的JDBC数据源,MySQL,Oracle等)推到目地数据源
- 有没有曾经为IE浏览器中长按钮莫名其妙的padding感到困扰?在分析解决方法之前,我们首先来看一下问题所在。在IE中,如果按钮文本比较长,
- 补充说明,外键:不要使用外键,一切外键概念都在应用层解决。补充说明,数据库的列,也就是字段名,尽量带上飘符号`数据库存在的意义:数据存储和数
- 有如下 borg pattern 的实现:class Borg(object): __shared_state = {}def
- io.BytesIO简要介绍及示例io.BytesIO 是 Python 内置的一个 I/O 类,用于在内存中读写二进制数据。它的作用类似于
- 在GUI编程中有一个不容忽视的部分,那就是布局管理。布局管理掌控着我们的控件在应用程序窗口如何摆放。布局管理可以通过两种方式来完成。我们可以
- 本文实例讲述了Python面向对象程序设计之类的定义与继承。分享给大家供大家参考,具体如下:定义类:class A: def _
- CREATE TABLE table1( [ID] [bigint] IDENTITY(1,1) NOT NULL, [Name] [nva
- 前言MySQL 服务器正确安装以后,可以通过命令行管理工具或者图形化的管理工具来操作 MySQL 数据库。MySQL 图形化管理工具极大地方
- PHP引擎php.ini参数优化无论是apache还是nginx,php.ini都是适合的。而php-fpm.conf适合nginx+fcg
- 什么是双端队列双端队列是与队列类似的有序集合。它有一前、一后两端,元素在其中保持自己的位置。与队列不同的是,双端队列对在哪一端添加和移除元素
- 对于Python开发用户来讲,安装第三方库是家常便饭,下面提供两种安装方式pycharm软件安装1.打开file>setting2.点
- 什么是变量在Python编程语言中,变量是用于存储数据值的标识符。它们可以用来引用数据值,而不是直接使用值本身。可以使用等号(=)运算符来将
- 一、前言今天有粉丝咨询了一个问题,他现在有两个列表,它们的元素都为字典,且字典都有一个key为id,现在想把这两个字典根据id合并为一个字典
- 一、简介Imageio是一个Python库,提供了一个简单的界面来读取和写入各种图像数据,包括动画图像,视频,体积数据和科学格式。它是跨平台
- 一旦你准备好了翻译,如果希望在Django中使用,那么只需要激活这些翻译即可。在这些功能背后,Django拥有一个灵活的模型来确定在安装和使