GO json 如何轉化為 map 和 struct


簡單談一些 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"`
}

  

說明幾點

  • 實際場景中,canal 消息的 data 結構是由表決定的,在 JSON 成功解析前無法提前知道,所以這里定義為 map[string]string;
  • 轉化的結構體成員必須是可導出的,所以成員變量名都是大寫,而與 JSON 的映射通過 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

據我所知,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 的使用,敲敲源碼中例子應該差不多了。

 

友情鏈接:https://zhuanlan.zhihu.com/p/66926495


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM