42

Go 中实例演示 json 的 map 和 struct 便捷处理转化

 4 years ago
source link: https://www.tuicool.com/articles/7j2M3mJ
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

今天简单谈一些 JSON 数据处理的小知识。近期工作中,因为要把数据库数据实时更新到 elasticsearch,在实践过程中遇到了一些 JSON 数据处理的问题。

实时数据

实时数据获取是通过阿里开源的 canal 组件实现的,并通过消息队列 kafka 传输给处理程序。我们将接收到的 JSON 数据类似如下的形式。

{
    "type": "UPDATE",
    "database": "blog",
    "table": "blog",
    "data": [
        {
            "blogId": "100001",
            "title": "title",
            "content": "this is a blog",
            "uid": "1000012",
            "state": "1"
        }
    ]
}

简单说下数据的逻辑,type 表示数据库事件是新增、更新还是删除事件,database 表示对应的数据库名称,table 表示相应的表名称,data 即为数据库中数据。

怎么处理这串 JSON 呢?

json 转化为 map

最先想到的方式就是通过 json.Unmarshal 将 JSON 转化 map[string]interface{}。

示例代码:

func main () {
    msg := []byte(`{
    "type": "UPDATE",
    "database": "blog",
    "table": "blog",
    "data": [
        {
            "blogId": "100001",
            "title": "title",
            "content": "this is a blog",
            "uid": "1000012",
            "state": "1"
        }
    ]}`)
    var event map[string]interface{}
    if err := json.Unmarshal(msg, &event); err != nil {
        panic(err)
    }

    fmt.Println(event)
}

打印结果如下:

map[data:[map[title:title content:this is a blog uid:1000012 state:1 blogId:100001]] type:UPDATE database:blog table:blog]

到此,就成功解析出了数据。接下来是使用它,但我觉得 map 通常有几个不足。

  • 通过 key 获取数据,可能出现不存在的 key,为了严谨,需要检查 key 是否存在;
  • 相对于结构体的方式,map数据提取不便且不能利用 IDE 补全检查,key 容易写错;

针对这个情况,可以怎么处理呢?如果能把 JSON 转化为struct 就好了。

json 转化为 struct

在 GO 中,json 转化为 struct 也非常方便,只需提前定义好转化的 struct 即可。我们先来定义一下转化的 struct。

type Event struct {
    Type     string              `json:"type"`
    Database string              `json:"database"`
    Table    string              `json:"table"`
    Data     []map[string]string `json:"data"`
}

说明几点

json:"tagName"

解析代码非常简单,如下:

e := Event{}
if err := json.Unmarshal(msg, &e); err != nil {
    panic(err)
}

fmt.Println(e)

打印结果:

{UPDATE blog blog [map[blogId:100001 title:title content:this is a blog uid:1000012 state:1]]}

接下来,数据的使用就方便了不少,比如事件类型获取,通过 event.Type 即可完成。不过,要泼盆冷水,因为 data 还是 []map[string]string 类型,依然有 map 的那些问题。

能不能把 map 转化为 struct ?

map 转化为 struct

据我所知,map 转为转化为 struct,GO 是没有内置的。如果要实现,需要依赖于 GO 的反射机制。

不过,幸运的是,其实已经有人做了这件事,包名称为 mapstructure ,使用也非常简单,敲一遍它提供的几个 例子 就学会了。README 中也说了,该库主要是遇到必须读取一部分 JSON 才能知道剩余数据结构的场景,和我的场景如此契合。

安装命令如下:

$ go get https://github.com/mitchellh/mapstructure

开始使用前,先定义 map 将转化的 struct 结构,即 blog 结构体,如下:

type Blog struct {
    BlogId  string `mapstructure:"blogId"`
    Title   string `mapstructrue:"title"`
    Content string `mapstructure:"content"`
    Uid     string `mapstructure:"uid"`
    State   string `mapstructure:"state"`
}

因为,接下来要用的是 mapstructure 包,所以 struct tag 标识不再是 json,而是 mapstructure。

示例代码如下:

e := Event{}
if err := json.Unmarshal(msg, &e); err != nil {
    panic(err)
}

if e.Table == "blog" {
    var blogs []Blog

    if err := mapstructure.Decode(e.Data, &blogs); err != nil {
        panic(err)
    }

    fmt.Println(blogs)
}

event 的解析和前面的一样,通过 e.Table 判断是是否来自 blog 表的数据,如果是,使用 Blog 结构体解析。接下来通过 mapstructure 的 Decode 完成解析。

打印结果如下:

[{100001 title this is a blog 1000012 1}]

到此,似乎已经完成了所有工作。非也!

弱类型解析

不知道大家有没有发现一个问题,那就是 Blog 结构体中的所有成员都是 string,这应该是 canal 做的事情,所有的值类型都是 string。但实际上 blog 表中的 uid 和 state 字段其实都是 int。

理想的结构体定义应该是下面这样。

type Blog struct {
    BlogId  string `mapstructure:"blogId"`
    Title   string `mapstructrue:"title"`
    Content string `mapstructure:"content"`
    Uid     int32  `mapstructure:"uid"`
    State   int32  `mapstructure:"state"`
}

但是当把新的 Blog 类型代入之前的代码,会如下的错误。

panic: 2 error(s) decoding:

* '[0].state' expected type 'int32', got unconvertible type 'string'
* '[0].uid' expected type 'int32', got unconvertible type 'string'

提示类型解析失败。其实,这种形式的 json 在其他一些软类型语言中也会出现。

那如何解决这个问题?提两种解决方案

  • 使用时进行转化,比如类型为 int 的数据,使用时可以用 strconv.Atoi 转化。
  • 使用 mapstructure 提供的软类型 map 转化 struct 的功能;

显然,第一种方式太 low,转化的时候还要多一步错误检查。那第二种方式如何呢?

来看示例代码,如下:

var blogs []Blog
if err := mapstructure.WeakDecode(e.Data, &blogs); err != nil {
    panic(err)
}

fmt.Println(blogs)

其实只需要把 mapstructure 的 Decode 替换成 WeakDecode 就行了,字如其意,弱解析。如此easy。

到此,才算完成!接下来的数据处理就简单很多了。如果想学习 mapstructure 的使用,敲敲源码中例子应该差不多了。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK