Golang實(shí)現(xiàn)Json分級(jí)解析及數(shù)字解析實(shí)踐詳解
一、背景介紹
在go語言開發(fā)過程中經(jīng)常需要將json字符串解析為struct,通常我們都是根據(jù)json的具體層級(jí)關(guān)系定義對(duì)應(yīng)的struct,然后通過json.Unmarshal()命令實(shí)現(xiàn)json到struct對(duì)象的轉(zhuǎn)換,然后再根據(jù)具體邏輯處理相應(yīng)的數(shù)據(jù)。
你是否遇到過在無法準(zhǔn)確確定json層級(jí)關(guān)系的情況下對(duì)json進(jìn)行解析的需求呢?
接下來我將介紹一次解析不確定的json對(duì)象的經(jīng)歷,以及遇到的問題和解決方法。
假設(shè)我們需要調(diào)用某個(gè)http接口從而獲得一件商品的相似品推薦結(jié)果,該服務(wù)的輸入?yún)?shù)只有一個(gè)skuId參數(shù),接口的返回?cái)?shù)據(jù)時(shí)json格式,并且同時(shí)存在兩種返回參數(shù)。我們無法通過入?yún)⑴袛嘟涌诜祷啬囊环N結(jié)果(估計(jì)這種異常是http服務(wù)開發(fā)者無意導(dǎo)致的,但是沒辦法,我們必須基于該服務(wù)接口開發(fā)某種功能。)
具體的,返回結(jié)果有兩種情況
第一種
{ "return": "0", "result":[ { "goods_id": 37278077211, .... "shop_name": "xxxxx", }, { "goods_id": 236492067349, .... "shop_name": "xxxxx", } ] }
第二種
{ "return": "0", "result": { "data": [ { "goods_id": 58054798450, ...... "shop_name": "xxxxxxxx" }, { "goods_id": 27395404673, ...... "shop_name": "xxxxxxxx" } ] } }
二、解決方案
(1)將Json直接解析為map
由于在解析前我們并不能確定result到底是一個(gè)struct還是一個(gè)Slice,因此我們也無法直接利用json.Unmarshal一步解出對(duì)應(yīng)的struct對(duì)象。好在我們知道所有json都可以直接解析成map[string]interface{}的結(jié)構(gòu),因此我們可以將json先轉(zhuǎn)化為map,然后根據(jù)結(jié)構(gòu)名key去決定后續(xù)的轉(zhuǎn)換流程,具體代碼如下:
var object interface{} var data interface{} err := json.Unmarshal([]byte(jsonStr),&object) if err != nil{ fmt.Printf("unmarshal %s error: %s\n",jsonStr,err.Error()) } //判斷returnCode ret := object.(map[string]interface{})["return"] if ret != 0{ fmt.Println("the response of http error") } //判斷result是何種類型 result := object.(map[string]interface{})["result"] resultType := reflect.TypeOf(result) if resultType.Kind() == reflect.Map{ data = result.(map[string]interface{})["data"] } if resultType.Kind() == reflect.Slice{ data = result } //解析goods_id var skuList []int64 for _,v := range data.([]interface{}){ preSku := v.(map[string]interface{})["goods_id"].(float64) skuList = append(skuList,int64(preSku)) } fmt.Printf("the skuLst = %+v\n",skuList)
這種方式的優(yōu)點(diǎn)是只需要Unmarshal一次,缺點(diǎn)是每一級(jí)都需要顯示的去做類型轉(zhuǎn)化,書寫起來比較繁瑣。尤其是json本身結(jié)構(gòu)復(fù)雜,其中只有一小部分需要確定具體類型的情況下,解析過程會(huì)更加繁瑣復(fù)雜。
那么是否可以只解析確定部分,不確定的部分先保留[]byte的原始格式,按map解析呢?
答案是肯定的。這時(shí)候就需要用到j(luò)son.RawMessage字段類型了
(2)解析部分json struct的方法 (json.RawMessage的用法)
在解析json過程中,有時(shí)我們可能只需要解析json的某一部分?jǐn)?shù)據(jù),比如,當(dāng)json中只有一部分是我們需要的數(shù)據(jù),或者我們需要先解析一部分?jǐn)?shù)據(jù),才能根據(jù)解析的部分?jǐn)?shù)據(jù)來決定剩余數(shù)據(jù)如何解析。我們繼續(xù)以上面的需求為例。此時(shí)需要我們預(yù)先定義需要解析的部分
type RespStruct struct { RetCode int `json:"return"` Result json.RawMessage `json:"result"` }
我們首先解析return字段。result字段內(nèi)容將繼續(xù)保持[]byte類型的狀態(tài)。接下來我們繼續(xù)解析剩余部分
var object RespStruct err := json.Unmarshal([]byte(jsonStr2),&object) if err != nil{ fmt.Printf("unmarshal %s error: %s\n",jsonStr,err.Error()) } //判斷returnCode if object.RetCode != 0{ fmt.Println("the response of http error") } //判斷result是何種類型 var data interface{} err = json.Unmarshal(object.Result,&data) if err != nil{ fmt.Printf("unmarshal %s error: %s\n",object.Result,err.Error()) } resultType := reflect.TypeOf(data) if resultType.Kind() == reflect.Map{ data = data.(map[string]interface{})["data"] } //解析goods_id var skuList []int64 for _,v := range data.([]interface{}){ preSku := v.(map[string]interface{})["goods_id"].(float64) skuList = append(skuList,int64(preSku)) } fmt.Printf("the skuLst = %+v\n",skuList)
看到這里,有人可能會(huì)產(chǎn)生疑問,我可不可以將不需要解析的字段定義為[]byte 類型呢,畢竟解析為rawMessage類型后,該字段本身也是[]byte。通過實(shí)驗(yàn)我們發(fā)現(xiàn)是不可以的,如果將rawMessage替換為 []byte類型,解析過程會(huì)返回錯(cuò)誤,因?yàn)樵趈son包中,雖然RawMessage類型時(shí) []byte的別名,但是解析過程中,只處理rawMessage類型(golang 是強(qiáng)類型編程語言)。
type RawMessage []byte
(3) json.Number類型的使用
在上個(gè)實(shí)驗(yàn)中,有些同學(xué)可能發(fā)現(xiàn),為什么goods_id字段的類型先由interface{}類型轉(zhuǎn)為float64,然后才被轉(zhuǎn)換為我們需要的int64呢?
這是因?yàn)樵?json 中是沒有整型和浮點(diǎn)型之分的,當(dāng)我們利用json 包中的 Unmarshal 方法將數(shù)字類型解析為interface{}時(shí),它就會(huì)將把所有數(shù)字類型全部轉(zhuǎn)換為和規(guī)范最接近的float64類型。如果我們希望更加方便的將數(shù)字類型準(zhǔn)換為指定的類型,就需要用到j(luò)son.Number這個(gè)類型。具體如下:
var object RespStruct err := json.Unmarshal([]byte(jsonStr),&object) if err != nil{ fmt.Printf("unmarshal %s error: %s\n",jsonStr,err.Error()) } //判斷returnCode if object.RetCode != 0{ fmt.Println("the response of http error") } //判斷result是何種類型 var data interface{} decoder := json.NewDecoder(bytes.NewReader(object.Result)) decoder.UseNumber() decoder.Decode(&data) resultType := reflect.TypeOf(data) if resultType.Kind() == reflect.Map{ data = data.(map[string]interface{})["data"] } //解析goods_id var skuList []int64 for _,v := range data.([]interface{}){ preSku,err := v.(map[string]interface{})["goods_id"].(json.Number).Int64() if err != nil{ fmt.Printf("get goods_id error") } skuList = append(skuList,preSku) } fmt.Printf("the skuLst = %+v\n",skuList)
利用json.Number類型可以讓我們很方便的將數(shù)字類型轉(zhuǎn)換成我們想要的類型,除了轉(zhuǎn)換為int64類型外,還支持轉(zhuǎn)換為float64和string。
為了進(jìn)一步了解json.Number的實(shí)現(xiàn),我們查看相關(guān)源碼可以發(fā)現(xiàn)
//decode.go // A Number represents a JSON number literal. type Number string // String returns the literal text of the number. func (n Number) String() string { return string(n) } // Float64 returns the number as a float64. func (n Number) Float64() (float64, error) { return strconv.ParseFloat(string(n), 64) } // Int64 returns the number as an int64. func (n Number) Int64() (int64, error) { return strconv.ParseInt(string(n), 10, 64) }
通過源碼我們可以發(fā)現(xiàn)json.Number本身是string類型,只是在json包中被定義了別名,然后通過封裝的三個(gè)方法,實(shí)現(xiàn)了將string轉(zhuǎn)換為int64和float64類型的方法。
以上就是Golang實(shí)現(xiàn)Json分級(jí)解析及數(shù)字解析實(shí)踐詳解的詳細(xì)內(nèi)容,更多關(guān)于Golang Json分級(jí)解析的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Go中使用單調(diào)時(shí)鐘獲得準(zhǔn)確的時(shí)間間隔問題
這篇文章主要介紹了Go中使用單調(diào)時(shí)鐘獲得準(zhǔn)確的時(shí)間間隔,在go語言中,沒有直接調(diào)用時(shí)鐘的函數(shù),可以通過?time.Now()?獲得帶單調(diào)時(shí)鐘的?Time?結(jié)構(gòu)體,并通過Since和Until獲得相對(duì)準(zhǔn)確的時(shí)間間隔,需要的朋友可以參考下2022-06-06golang中實(shí)現(xiàn)給gif、png、jpeg圖片添加文字水印
這篇文章主要介紹了golang中實(shí)現(xiàn)給gif、png、jpeg圖片添加文字水印,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-04-04golang 結(jié)構(gòu)體初始化時(shí)賦值格式介紹
這篇文章主要介紹了golang 結(jié)構(gòu)體初始化時(shí)賦值格式介紹,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-12-12Go實(shí)現(xiàn)mongodb增刪改查工具類的代碼示例
這篇文章主要給大家介紹了關(guān)于Go實(shí)現(xiàn)mongodb增刪改查工具類的相關(guān)資料,MongoDB是一個(gè)NoSQL數(shù)據(jù)庫,它提供了靈活的文檔存儲(chǔ)模型以及強(qiáng)大的查詢和操作功能,需要的朋友可以參考下2023-10-10golang使用viper加載配置文件實(shí)現(xiàn)自動(dòng)反序列化到結(jié)構(gòu)
這篇文章主要為大家介紹了golang使用viper加載配置文件實(shí)現(xiàn)自動(dòng)反序列化到結(jié)構(gòu)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08golang?http請(qǐng)求未釋放造成的錯(cuò)誤問題
這篇文章主要介紹了golang?http請(qǐng)求未釋放造成的錯(cuò)誤問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01