golang之json.RawMessage

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 类型效率更高。从实现上看非常简单,只是封装了一层字节数组的交互,大家可以放心使用。 

 

 

 

 

 

posted @ 2024-04-26 12:52  X-Wolf  阅读(9)  评论(0编辑  收藏  举报