zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    HappyLeetcode50:Rotate Array
    【python,排序】几种常用的排序算法,使用python实现
    【python,logging】python中的logging模块
    【Python Lib】解析HTML利器 BeautifulSoup
    CSS 创建方式与优先级
    Python 正则表达式
    Python文件IO
    Python Thread
    Python 2.x and 3.x String VS Bytes
    Python Socket
  • 原文地址:https://www.cnblogs.com/akidongzi/p/12036096.html
Copyright © 2011-2022 走看看