Go语言:如何方便地提取Json字段?

简述

Json是http/https协议中十分常见的报文格式之一,以其简单明了的特性,深受前后端工程师的青睐。由于前端通常采用javascript解析后端发送过来的json数据,而javascript是动态语言,所以解析json变得很容易。后端的技术栈种类繁多,可以使用动态语言,例如python/ruby等,更多情况下会采用静态编译型语言,像Java/C#/Go等。

反序列化

1、动态语言的Json反序列化
通常反序列化为通用类型,比如字典,object对象等,由于动态语言编写时,不会校验类型,所以可以动态地获取字段,举一个Python的例子:

import json
j_str = '{"header":{"user":"admin"}}'
d = json.loads(j_str)
header = d["header"]

从上面的例子可以看出json模块将字符串反序化成动态字典,可以很方便地获取字典的key,但也有弊端,至少有两点:1)性能低 2)很可能有运行时异常(假设前端或者移动端发过来的报文格式变了)

2、静态语言
反序列化得到的类型,一般有两种形式:一种是强类型的class,一种是通用类型,例如Java中的Map<String, Object>类型。反序列化的库一般都会采用泛型方法支持这两种情况。
这两种方式都各有优缺点:
1)class类型
优点:一旦反序列化后,获取读取Json的数据,很方便。
缺点:如果报文五花八门,需要创建很多这样的类来对应不一样的报文格式。

2)Map类型
优点:不需要额外创建class类型来匹配不同的报文格式
缺点:读取json数据的时候,每个object可能都需要做强制类型转换,代码会十分难看,且效率低下

两种方式均有在采用,个人认为可能使用class的情况似乎更好一些。

Go: Json反序列化

Go是静态编译型语言,所以以上两种方式都是支持:

type Person struct {
     Name string `json:"name"`
     Age int `json:"age"`
} 

jStr := `{"name":"xiaoming","age":18}`
var p Person
err := json.Unmarshal([]byte(jStr), &p)
if err == nil{
     fmt.Print(p.Name, p.Age)
}
var m  map[string]interface{}
err := json.Unmarshal(jsonBytes, &m)

当然优缺点也如上面陈述的一样。

那么如何方便地获取数据呢?

我们想到一种方式: 如果能像python一样轻松获取,还不失去作为静态语言的性能优势。那是不是就能起飞了,于是先想到怎么使用,再来阐述如何实现,我希望这样使用:

jsonStr := `{
    "links": {
      "self": "/domains/test.one"
    },
    "data": {
        "type": "domains",
        "id": "test.one",
        "attributes": {
            "product": " Website",
            "package": "Professional",
            "created_at": "2016-08-19T11:37:01Z"
        }
    }
}`
m, _ := types.ConvJson2Map([]byte(jsonStr))
fmt.Println(m.Get("data").Get("Type").Value())

是不是和python很相似。那么我们就要实现一个函数:ConvJson2Map,返回的对象支持Get方法。

func ConvJson2Map(jsonBytes []byte) (*KeyStringMap, error){

}

返回类型的接口,假设叫IJsonValue,除了支持Get,还有其他方法:

package types

type IJsonValue interface {
    Get(key string) IJsonValue
    DeepGet(key string) IJsonValue
    ContainsKey(key string) bool

    GetAt(index int) IJsonValue

    Contains(v interface{}) bool

    Value() interface{}

    IsMap() bool
    IsSliceOrArray() bool
}

那么我们只要实现ConvJson2Map方法,似乎就能把我们的想法落地了。实现过程是一些类型转换,遍历算法等,就不赘述了。直接贴代码。

package types

import (
    "encoding/json"
    "reflect"
)

type KeyStringMap struct {
    innerMap map[string]interface{}
}

type KSMapEntry struct {
    Key   interface{}
    Value interface{}
}

type JsonValue struct {
    value interface{}
}

func (j JsonValue) IsMap() bool {
    return reflect.TypeOf(j.value).Kind() == reflect.Map
}

func (j JsonValue) IsSliceOrArray() bool {
    return reflect.TypeOf(j.value).Kind() == reflect.Slice || reflect.TypeOf(j.value).Kind() == reflect.Array
}

func (j JsonValue) Get(key string) IJsonValue {
    if j.IsMap() {
        m := ConvKSMap(j.value)
        if vv := m.Get(key); vv != nil {
            return JsonValue{value: vv.Value()}
        } else {
            return nil
        }
    }
    return nil
}

func deepGet(j IJsonValue, key string) IJsonValue {
    if v := j.Get(key); v != nil {
        return v
    } else {
        m := ConvKSMap(j.Value())
        values := m.Values()
        maps := values.Filter(func(v interface{}) bool {
            return reflect.TypeOf(v).Kind() == reflect.Map
        })
        if maps.Len() == 0 {
            return nil
        } else {
            for i := 0; i < maps.Len(); i++ {
                jj := JsonValue{value: maps.GetAt(i)}
                rr := deepGet(jj, key)
                if rr != nil && rr.Value() != nil {
                    return rr
                }
            }
        }
    }
    return nil
}

func (j JsonValue) DeepGet(key string) IJsonValue {
    if j.IsMap() {
        return deepGet(j, key)
    }
    return nil
}

func (j JsonValue) GetAt(index int) IJsonValue {
    if j.IsSliceOrArray() {
        m := ConvSlice(j.value)
        vv := m.GetAt(index)
        return JsonValue{value: vv}
    }
    return nil
}

func (j JsonValue) ContainsKey(key string) bool {
    if j.IsMap() {
        m := ConvKSMap(j.value)
        return m.ContainsKey(key)
    }
    return false
}

func (j JsonValue) Contains(v interface{}) bool {
    if j.IsSliceOrArray() {
        s := ConvSlice(j.value)
        return s.Contains(v)
    }
    return false
}

func (j JsonValue) Value() interface{} {
    return j.value
}

func (m KeyStringMap) Get(key string) IJsonValue {
    if v, ok := m.innerMap[key]; ok {
        return JsonValue{value: v}
    } else {
        return nil
    }
}

func mapDeepGet(m *KeyStringMap, key string) IJsonValue {
    if v := m.Get(key); v != nil {
        return v
    } else {
        values := m.Values()
        maps := values.Filter(func(v interface{}) bool {
            return reflect.TypeOf(v).Kind() == reflect.Map
        })
        if maps.Len() == 0 {
            return nil
        } else {
            for i := 0; i < maps.Len(); i++ {
                jj := ConvKSMap(maps.GetAt(i))
                rr := mapDeepGet(jj, key)
                if rr != nil && rr.Value() != nil {
                    return rr
                }
            }
        }
    }
    return nil
}

func (m *KeyStringMap) DeepGet(key string) IJsonValue {
    if v, ok := m.innerMap[key]; ok {
        return JsonValue{value: v}
    } else {
        return mapDeepGet(m, key)
    }
}

func (m *KeyStringMap) Set(key string, value interface{}) {
    m.innerMap[key] = value
}

func (m KeyStringMap) ContainsKey(key string) bool {
    if _, ok := m.innerMap[key]; ok {
        return true
    } else {
        return false
    }
}

func (m KeyStringMap) Keys() *Slice {
    ret := make([]interface{}, 0)
    for k, _ := range m.innerMap {
        ret = append(ret, k)
    }
    return ConvSlice(ret)
}

func (m KeyStringMap) Values() *Slice {
    ret := make([]interface{}, 0)
    for _, v := range m.innerMap {
        ret = append(ret, v)
    }
    return ConvSlice(ret)
}

func (m KeyStringMap) EntrySet() []KSMapEntry {
    ret := make([]KSMapEntry, 0)
    for k, v := range m.innerMap {
        entry := KSMapEntry{Key: k, Value: v}
        ret = append(ret, entry)
    }
    return ret
}

func (m KeyStringMap) ContainsValue(value interface{}) bool {
    values := m.Values()
    for _, e := range values.innerSlice {
        if e == value {
            return true
        }
    }
    return false
}

func NewKSMap() *KeyStringMap {
    m := make(map[string]interface{})
    iMap := &KeyStringMap{innerMap: m}
    return iMap
}

func ConvKSMap(m interface{}) *KeyStringMap {
    iMap := &KeyStringMap{innerMap: m.(map[string]interface{})}
    return iMap
}

func ConvJson2Map(jsonBytes []byte) (*KeyStringMap, error) {
    m := NewKSMap()
    err := json.Unmarshal(jsonBytes, &m.innerMap)
    return m, err
}

至此我们就能像python一样方便去读json数据了。
聪明的你,Get到了吗?

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,755评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,369评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,799评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,910评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,096评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,159评论 3 411
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,917评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,360评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,673评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,814评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,509评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,156评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,123评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,641评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,728评论 2 351

推荐阅读更多精彩内容

  • 简介 什么是 Google Protocol Buffer? 假如您在网上搜索,应该会得到类似这样的文字介绍: G...
    Ucan先生阅读 1,090评论 0 0
  • 微服务架构已成为目前互联网架构的趋势,关于微服务的讨论,几乎占据了各种技术大会的绝大多数版面。国内使用最多的服务治...
    阿斯蒂芬2阅读 6,062评论 0 2
  • 前言 在上一篇 文章 中已经介绍了 枚举 类型字段的使用,本文接着介绍 JSON 类型字段的使用。 关于 JSON...
    anyesu阅读 5,583评论 3 1
  • 1. Json格式 完整的一条json语句中,字段都为字符串类型,值为基本数据类型:整形、布尔型、字符串等 2. ...
    阿斯巴甜不太甜阅读 866评论 0 0
  • 有的时候,用于序列化、反序列化的类型中字段的名称,和JSON字符串中的字段的名称可能不是规范的映射关系,这个时候我...
    CokeCode阅读 7,665评论 0 0