go 解析json

在實際接觸json之前,我以爲json全都是長這樣的。

{
    "id":6,
    "name":"hbliuu"
    "marry":false
}

嗯,如果是這樣,也就沒必要寫這篇blog了。

實際上呢,第一次自己寫程序的時候,遇到的json是這樣的。

{
    "name": "jero",
    "parents": {
        "father": "hbliuu",
        "mother": "unknown"
    },
    "body": [
        {
            "height": 175
        },
        {
            "weight": 65
        }
    ]
}

實際比這複雜多了,但是不能直接複製過來,怕泄漏公司機密,只能自己編一個。。。

說它複雜,體現在兩點,第一,裏面有{},第二,裏面有[]。那實際是怎麼個更復雜法呢?其實就是更多的信息,更多層的嵌套。

簡單json的解析

我本來覺得這部分很簡單,想寫個demo一貼了事的,結果寫demo的過程中,碰了一鼻子灰。。。

所以還是仔細記錄一下幾個需要注意的點吧。

package main

import (
	"encoding/json"
	"fmt"
)

type test struct {
	Id     int    `json:"identity"`
	Name   string
	Marry  bool
}

func main() {
	data:="{\"identity\":666,\"name\":\"hbliuu\",\"marry\":false}"
	str:=[]byte(data)

	res:=test{}
	err:=json.Unmarshal(str,&res)
	if err!=nil{
		fmt.Println(err)
	}

	fmt.Println(res)
}

上面給出了一個能用的demo,注意這個demo裏有很多細節:

1.結構體定義,每個變量首字母都要大寫

這個呢,是實踐出來的,如果沒有大寫,倒也不會報錯,只是解析出來,int就是0,string就是“”,也就是說,根本沒解析。

爲什麼會這樣呢,因爲解析的函數在json包裏,而結構體定義是在你自己寫的那個包裏,如果結構體變量首字母小寫,在json包裏就是不可見的。關於go可見性規則,推薦閱讀Golang可見性規則(公有與私有,訪問權限)

2.結構體定義,“註釋”可以沒有

這個東西不知道官方稱呼是什麼,姑且稱之爲註釋吧,這個東西是幹什麼的呢?是用來告訴你,解析的時候,對應的字段key是什麼,那什麼時候可以沒有呢,就是你結構體變量名和json裏實際字段key的名稱一樣的時候,反之,如果不一樣,那麼必須有這個註釋,說明實際的key是什麼,不然是解析不出來的。說得可能有些抽象,看我的demo就明白了。

3.json的key不區分大小寫

也就是說,如果不管結構體裏是Name還是NaMe,效果是一樣的,都可以用來解析實際 json裏key爲name或者NAME或者任意其他大小寫組合。

複雜json的解析

複雜的json,如果還是定義結構體來解析,那結構體就得寫半天。對於json來說是括號裏套着括號,對於結構體,那就是結構體裏套着結構體。

有時候,一個複雜的json消息體裏,只有一兩個字段是我們需要的,所以沒必要定義一個結構體,把每個字段都解析出來。另外,有時候你收到的json,是一個不固定的格式,這個時候自然也沒法把json解析到定義好的結構體裏了。

不用結構體用什麼呢?我們可以看一下unmarshal函數的原型。

第二個參數,並不是結構體,而是接口,只不過在go語言裏,一切都是接口,所以往裏面傳結構體,自然也是沒問題的。

下面我們就看看,直接傳個接口,結果會是什麼樣的。首先對剛纔的demo做如下修改:

	//res := test{}
	var res interface{}

運行結果如下:

嗯,這個打印表示,unmarshal函數直接把json解析成了一個map。

但實際上,這個時候res還是一個interface,需要轉換成map,才能用map的操作,就像下面這樣。

	if err:=json.Unmarshal(str,&res); err!=nil{
		fmt.Println(err)
	} else {
		fmt.Println(res)
		if resMap, ok := res.(map[string]interface{}); !ok {
			fmt.Println("interface to map failed")
		} else {
			fmt.Println(resMap["name"])
		}
	}

 結果如下:

 注意,上面的類型轉換,使用的是一種安全的寫法,如果不對類型轉換的結果進行判斷,如果類型轉換出錯,就會panic。實際場景裏,你沒法保證你的程序接收到的json消息就是你預想的那種格式的,萬一別人把一個其他格式的消息不小心發送到你的程序了呢,所以,一定要對每次類型轉換的結果進行判斷!

歐克,總結一下以上的思路,首先,將json解析成一個interface,然後,使用interface的類型轉換,把interface裏面你需要的內容,給取出來。

下面就解析一下最開始給的那個複雜的例子。

一步一步來,首先,看看直接解析成interface是啥樣的。首先data改成下面這個:

	dataComplex := "{" +
		"\"name\": \"jero\", " +
		"\"parents\": {\"" +
		"father\": \"hbliuu\", " +
		"\"mother\": \"unknown\"" +
		"}," +
		"\"body\": " +
		"[" +
		"{\"height\": 175}," +
		"{\"weight\": 65}" +
		"]" +
		"}"

解析出來結果如下: 

首先,整體是一個map,map裏面,body對應的value是一個數組,數組裏有兩個map,而parents對應的value又是一個 map,接下來我們解析body裏的一個字段。

首先把body解析成一個數組:

			if resMap["body"] == nil {
				fmt.Println("error body!")
			} else if bodyArray, ok := resMap["body"].([]interface{}); !ok {
				fmt.Println("interface to array failed!")
			} else {
				fmt.Println(bodyArray)
			}

結果如下:

可以看到,這個數組由兩個map組成,接下來就是在把interface轉成map。這裏再次強調,一定做好檢查!

 

			if resMap["body"] == nil {
				fmt.Println("error body!")
			} else if bodyArray, ok := resMap["body"].([]interface{}); !ok {
				fmt.Println("interface to array failed!")
			} else if bodyMap, ok := bodyArray[0].(map[string]interface{}); !ok {
				fmt.Println("interface to map failed!")
			} else if bodyMap["height"] != nil {
				fmt.Println("height is:", bodyMap["height"])
			}

結果如下:

到這裏,還沒完!這裏我們打印的,還是一個interface,而不是我們預想的數值類型,還需要再進行一次類型轉換,轉成float64,這樣才能滿足後續處理的需求。下面給出完整的最終版代碼。

package main

import (
	"encoding/json"
	"fmt"
)

type test struct {
	Id     int    `json:"identity"`
	Name   string
	Marry  bool
}

func main() {
	//data:="{\"identity\":666,\"name\":\"hbliuu\",\"marry\":false}"
	dataComplex := "{" +
		"\"name\": \"jero\", " +
		"\"parents\": {\"" +
		"father\": \"hbliuu\", " +
		"\"mother\": \"unknown\"" +
		"}," +
		"\"body\": " +
		"[" +
		"{\"height\": 175}," +
		"{\"weight\": 65}" +
		"]" +
		"}"
	str:=[]byte(dataComplex)

	//res := test{}
	var res interface{}
	if err:=json.Unmarshal(str,&res); err!=nil{
		fmt.Println(err)
	} else {
		fmt.Println(res)
		if resMap, ok := res.(map[string]interface{}); !ok {
			fmt.Println("interface to map failed")
		} else {
			fmt.Println(resMap["name"])
			if resMap["body"] == nil {
				fmt.Println("error body!")
			} else if bodyArray, ok := resMap["body"].([]interface{}); !ok {
				fmt.Println("interface to array failed!")
			} else if bodyMap, ok := bodyArray[0].(map[string]interface{}); !ok {
				fmt.Println("interface to map failed!")
			} else if bodyMap["height"] == nil {
				fmt.Println("cannot find height!")
			} else if height, ok := bodyMap["height"].(float64); !ok {
				fmt.Println("height is not float64!")
			} else {
				fmt.Println("height is:", height)
			}
		}
	}
}

 這裏有一個疑點,height明明是175,是一個int,爲什麼要轉成float?這個呢,我也不清楚,但是!只要是數值型的,這裏就只能先把interface轉成float64,否則就會轉換失敗。

最後再次強調,類型轉換時的判斷不能省略!雖然看着上面的代碼,爲了解析一個字段寫了這麼老長,感覺有些不適,但這些代碼都是必不可少的,如果一大坨放在這你覺的不舒服,可以把接口轉換這一塊封裝到一個函數裏,但一定得有。實際生產中的代碼,我們是一定要杜絕任何可能導致panic的源頭!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章