今天简单谈一些 JSON 数据处理的小知识。近期工做中,由于要把数据库数据实时更新到 elasticsearch,在实践过程当中遇到了一些 JSON 数据处理的问题。git
实时数据获取是经过阿里开源的 canal 组件实现的,并经过消息队列 kafka 传输给处理程序。咱们将接收到的 JSON 数据相似以下的形式。github
{
"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
最早想到的方式就是经过 json.Unmarshal 将 JSON 转化 map[string]interface{}。bash
示例代码:数据结构
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)
}
复制代码
打印结果以下:elasticsearch
map[data:[map[title:title content:this is a blog uid:1000012 state:1 blogId:100001]] type:UPDATE database:blog table:blog]
复制代码
到此,就成功解析出了数据。接下来是使用它,但我以为 map 一般有几个不足。学习
针对这个状况,能够怎么处理呢?若是能把 JSON 转化为struct 就行了。ui
在 GO 中,json 转化为 struct 也很是方便,只需提早定义好转化的 struct 便可。咱们先来定义一下转化的 struct。this
type Event struct {
Type string `json:"type"`
Database string `json:"database"`
Table string `json:"table"`
Data []map[string]string `json:"data"`
}
复制代码
说明几点
json:"tagName"
的 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,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 在其余一些软类型语言中也会出现。
那如何解决这个问题?提两种解决方案
显然,第一种方式太 low,转化的时候还要多一步错误检查。那第二种方式如何呢?
来看示例代码,以下:
var blogs []Blog
if err := mapstructure.WeakDecode(e.Data, &blogs); err != nil {
panic(err)
}
fmt.Println(blogs)
复制代码
其实只须要把 mapstructure 的 Decode 替换成 WeakDecode 就好了,字如其意,弱解析。如此easy。
到此,才算完成!接下来的数据处理就简单不少了。若是想学习 mapstructure 的使用,敲敲源码中例子应该差很少了。