Golang處理JSON(一)--- 編碼

JSON

http的交互的生命周期包含請求和響應。前面我們介紹了很多關于發起請求,處理請求的內容。現在該聊一聊返回響應內容了。對于web服務的響應,以前常見的響應是返回服務端渲染的模板。瀏覽器只要展示模板即可。隨著Restful風格的api出現,已經前后端分離,更多的返回格式是json字串。本節我們將討論在golang中如何編碼和解碼json。

JSON是一種數據格式描述語言。以key和value構成的哈系結構,類似javascript中的對象,python中的字典。通常json格式的key是字串,其值可以是任意類型,字串,數字,數組或者對象結構。更多關于json的可以訪問JSON了解。

數據結構map

json源于javascript的對象結構,golang中直接對應的數據結構,可是golang的map也是key-value結構,同時struct結構體也可以描述json。當然,對于json的數據類型,go也會有對象的結構所匹配。大致對應關系如下:

數據類型 JSON Golang
字串 string string
整數 number int64
浮點數 number flaot64
數組 arrary slice
對象 object struct
布爾 bool bool
空值 null nil

基本結構編碼

golang提供了encoding/json的標準庫用于編碼json。大致需要兩步:

  1. 首先定義json結構體。
  2. 使用 Marshal方法序列化。

定義結構體的時候,只有字段名是大寫的,才會被編碼到json當中。

type Account struct {
    Email string
    password string
    Money float64
}

func main() {
    account := Account{
        Email: "rsj217@gmail.com",
        password: "123456",
        Money: 100.5,
    }

    rs, err := json.Marshal(account)
    if err != nil{
        log.Fatalln(err)
    }

    fmt.Println(rs)
    fmt.Println(string(rs))
}

可以看到輸出如下,Marshal方法接受一個空接口的參數,返回一個[]byte結構。小寫命名的password字段沒有被編碼到json當中,生成的json結構字段和Account結構一致。

[123 34 69 109 97 105 108 34 58 34 114 115 106 50 49 55 64 103 109 97 105 108 46 99 111 109 34 44 34 77 111 110 101 121 34 58 49 48 48 46 53 125]
{"Email":"rsj217@gmail.com","Money":100.5}

復合結構編碼

相比字串,數字等基本數據結構,slice切片,map圖則是復合結構。這些結構編碼也類似。不過map的key必須是字串,而value必須是同一類型的數據。

type User struct {
    Name    string
    Age     int
    Roles   []string
    Skill   map[string]float64
}

func main() {

    skill := make(map[string]float64)

    skill["python"] = 99.5
    skill["elixir"] = 90
    skill["ruby"] = 80.0

    user := User{
        Name:"rsj217",
        Age: 27,
        Roles: []string{"Owner", "Master"},
        Skill: skill,
    }

    rs, err := json.Marshal(user)
    if err != nil{
        log.Fatalln(err)
    }
    fmt.Println(string(rs))
}

輸入:

{
    "Name":"rsj217",
    "Age":27,
    "Roles":[
        "Owner",
        "Master"
    ],
    "Skill":{
        "elixir":90,
        "python":99.5,
        "ruby":80
    }
}

嵌套編碼

slice和map可以匹配json的數組和對象,當前提是對象的value是同類型的情況。更通用的做法,對象的key可以是string,但是其值可以是多種結構。golang可以通過定義結構體實現這種構造:

type User struct {
    Name    string
    Age     int
    Roles   []string
    Skill   map[string]float64
    Account Account
}

func main(){
    
    ...

    user := User{
        Name:"rsj217",
        Age: 27,
        Roles: []string{"Owner", "Master"},
        Skill: skill,
        Account:account,
    }
    ...
}

輸出:

{
    "Name":"rsj217",
    "Age":27,
    "Roles":[
        "Owner",
        "Master"
    ],
    "Skill":{
        "elixir":90,
        "python":99.5,
        "ruby":80
    },
    "Account":{
        "Email":"rsj217@gmail.com",
        "Money":100.5
    }
}

通過定義嵌套的結構體Account,實現了key與value不一樣的結構。golang的數組或切片,其類型也是一樣的,如果遇到不同數據類型的數組,則需要借助空結構來實現:

type User struct {
    ...

    Extra []interface{}
}

extra := []interface{}{123, "hello world"}

user := User{
    ...
    
    Extra:   extra,
}

輸出:

{
    ...
    "Extra":[
        123,
        "hello world"
    ]
}

使用空接口,也可以定義像結構體實現那種不同value類型的字典結構。當空接口沒有初始化其值的時候,零值是 nil。編碼成json就是 null

type User struct {
    Name    string
    Age     int
    Roles   []string
    Skill   map[string]float64
    Account Account

    Extra []interface{}

    Level map[string]interface{}
}

func main() {

    ...

    level := make(map[string]interface{})

    level["web"] = "Good"
    level["server"] = 90
    level["tool"] = nil

    user := User{
        Name:    "rsj217",
        Age:     27,
        Roles:   []string{"Owner", "Master"},
        Skill:   skill,
        Account: account,
        Level:   level,
    }

    ...
}

輸出:

{
    ...
    "Extra":null,
    "Level":{
        "server":90,
        "tool":null,
        "web":"Good"
    }
}

可以看到 Extra返回的并不是一個空的切片,而是null。同時Level字段實現了向字典的嵌套結構。

StructTag 字段重名

通過上面的例子,我們看到了Level字段中的keyserver等是小寫字母,其他的都是大寫字母。因為我們在定義結構的時候,只有使用大寫字母開頭的字段才會被導出。而通常json世界中,更盛行小寫字母的方式。看起來就成了一個矛盾。其實不然,golang提供了struct tag的方式可以重命名結構字段的輸出形式。

type Account struct {
    Email    string  `json:"email"`
    Password string  `json:"pass_word"`
    Money    float64 `json:"money"`
}

func main() {
    account := Account{
        Email:    "rsj217@gmail.com",
        Password: "123456",
        Money:    100.5,
    }

    rs, err := json.Marshal(account)
    ...
}

我們使用struct tag,重新給Aaccount結構的字段進行了重命名。其中email小寫了,并且password字段還使用了下劃線,輸出的結果如下:

{"email":"rsj217@gmail.com","pass_word":"123456","money":100.5}
-忽略字段

重命名的可以一個利器,這個利器還提供了更高級的選項。通常使用marshal的時候,會把結構體的所有除了私有字段都編碼到json,而實際開發中,我們定義的結構可能更通用,我們需要某個字段可以導出,但是又不能編碼到json中。

此時使用 struact tag的 -符號就能完美解決,我們已經知道_常用于忽略字段的占位,在tag中則使用短橫線-。

type Account struct {
    Email    string  `json:"email"`
    Password string  `json:"password,omitempty"`
    Money    float64 `json:"money"`
}

輸出:

{"email":"rsj217@gmail.com","money":100.5}

可見即使Password不是私有字段,因為-忽略了它,因此沒有被編碼到json輸出。

omitempty可選字段

對于另外一種字段,當其有值的時候就輸出,而沒有值(零值)的時候就不輸出,則可以使用另外一種選項omitempty。

type Account struct {
    Email    string  `json:"email"`
    Password string  `json:"password,omitempty"`
    Money    float64 `json:"money"`
}
func main() {
    account := Account{
        Email:    "rsj217@gmail.com",
        Password: "",
        Money:    100.5,
    }
    ...
}

此時password不會被編碼到json輸出中。

string選項

gplang是靜態類型語言,對于類型定義的是不能動態修改。在json處理當中,struct tag的string可以起到部分動態類型的效果。有時候輸出的json希望是數字的字符串,而定義的字段是數字類型,那么就可以使用string選項。

type Account struct {
    Email    string  `json:"email"`
    Password string  `json:"password,omitempty"`
    Money    float64 `json:"money,string"`
}
func main() {
    account := Account{
        Email:    "rsj217@gmail.com",
        Password: "123",
        Money:    100.50,
    }

    ...
}

可以看到輸出為 money: "100.5", money字段的值是字串。(其實能轉換成100.50會比轉換成100.5更好,可是我沒有找到通過tag的方式實現 :()。

總結

上面所介紹的大致覆蓋了golang的json編碼處理。總體原則分兩步,首先定義需要編碼的結構,然后調用encoding/json標準庫的Marshal方法生成json byte數組,轉換成string類型即可。

golang和json的大部分數據結構匹配,對于復合結構,go可以借助結構體和空接口實現json的數組和對象結構。通過struct tag可以靈活的修改json編碼的字段名和輸出控制。

既然有JSON的編碼,當然就會有JSON的解碼。相比編碼JSON,解析JSON對于golang則需要更多的技巧。下一節我們聊一聊golang解析json相關的內容。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,119評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,382評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,038評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,853評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,616評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,112評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,192評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,355評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,869評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,727評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,928評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,467評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,165評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,570評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,813評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,585評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,892評論 2 372

推薦閱讀更多精彩內容