今天簡單談一些 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 的使用,敲敲源碼中例子應該差很少了。