开发者

Golang json 库中的RawMessage功能原理

目录

    json 作为一种通用的编解码协议,可阅读性上比 thrift,protobuf 等协议要好一些,同时编码的 size 也会比 XML 这类协议要小,在市面上用的非常多。甚至在很多业务上,我们的线上实例消耗最大的部分就是 json 的序列化和反序列化。这也是为什么很多 Gopher 会致力于研究怎样最有效地优化这个过程。

    今天我们来学习一个 golang 官方 json 库提供了一个经典能力:RawMessage。

    什么是序列化

    首先我们思考一下所谓序列化指的是什么呢?

    参考 json 包中 Marshaler 和 Unmarshaler 两个接口定义:

    go复制代码// 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 的说明。我们直接来看看源码中的实现:

    go复制代码// 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 pythonbut we choose to keep it in its raw form — pythona byte array.

    我们可以把 RawMessage 看作是一部分可以暂时忽略的信息,以后可以进一步去解析,但此时不用。所以,我们保留它的原始形式,还是个字节数组即可。

    使用场景

    软件开发中,我们经常说不要过度设计,好的代码应当有明确的使用场景,而且能高效地解决一类问题,而不是在设想和概念上造出来一个未经过验证的空中楼阁。

    那么 RawMessage 是不是这样一个空中楼阁呢?其实并不是。

    我们可以将其当做一个【占位符】。设想一下,我们给某种业务场景定义了一个通用的 model,其中部分数QFcxZq据需要在不同场景下对应不同的结构体。这个时候怎么 Marshal 成字节数组,存入数据库,以及读出数据,还原出 model 呢?

    我们就可以将这个可变的字段定义为 json.RawMessage,利用它适配万物的能力来进行读写。

    复用预计算的 json 值

    go复制代码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,所以我们可以用

    go复制代码json.RawMessage(`{"precomputed": true}`)

    来将一个字符串转换为 RawMessage。随后对其进行 Marshal,输出的结果如下:

    css复制代码{

        "header": {

            "precomputed": true

        },

        "body": "Hello Gophers!"

    }

    发现了么?

    这里 "precomputed": true 跟我们构造的 RawMessage 是一模一样的,所以对应到第一个能力:在序列化时使用一个预先计算好的 json 值。

    延迟解析 json 结构

    go复制代码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

        &nandroidbsp;   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.http://www.devze.comRawMessage 再次 Unmarshal,拿到最终的数据。

    上面的示例输出结果如下:

    复制代码YCbCr &{255 0 -10}

    RGB &{98 218 255}

    总结

    json 提供的 RawMessage 是直接暴露了底层的 []byte 作为交互凭证,它可以被内嵌在各种结构体中。作为不可变的字段类型的 placeholder,延迟解析。相较于 string 类型效率更高。从实现上看非常简单,只是封装了一层字节数组的交互,大家可以放心使用。

     

    以上就是Golang json 库中的RawMessage功能原理的详细内容,更多关于Golang json库RawMessage的资料请关注我们其它相关文章!

    0

    上一篇:

    下一篇:

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    最新开发

    开发排行榜