如何控制Go編碼JSON數據時的行爲

今天來聊一下我在Go中對數據進行 JSON 編碼時遇到次數最多的三個問題以及解決方法,你們來看看是否是也爲這些問題撓掉了很多頭髮。前端

自定義JSON鍵名

這個問題加到文章裏我是有所猶豫的,由於基本上你們都會,不過屬於同類問題我仍是放進來了,對新接觸 Go 的同窗更友好些。數據庫

咱們先從最多見的一個問題說,首先在Go 程序中要將數據編碼成JSON 格式時一般咱們會先定義結構體類型,將數據存放到結構體變量中。json

type Address struct {
    Type    string
    City    string  
    Country string
}

type CreditCard struct {
    FirstName string
    LastName  string
    Addresses []*Address
    Remark    string
}

home := &Address{"private", "Aartselaar", "Belgium"}
office := &Address{"work", "Boom", "Belgium"}
card := VCard{"Jan", "Kersschot", []*Address{home, office}, "none"}

js, err := json.Marshal(card)
fmt.Printf("JSON format: %s", js)

只有導出的結構體成員纔會被編碼,這也就是咱們爲何選擇用大寫字母開頭的字段名稱。在編碼時,默認使用結構體字段的名字做爲JSON對象中的key,可是通常JSON 是給HTTP接口返回數據使用的,在接口的規範裏針對數據咱們通常都要求返回snake case風格的字段名。解決這個問題的方法是在結構體聲明時在結構體字段標籤裏能夠自定義對應的JSON key數組

因此咱們把結構體聲明改成以下便可:app

type Address struct {
    Type    string  `json:"type"`
    City    string  `json:"city"`
    Country string  `json:"country"`
}

編碼JSON時忽略掉指定字段

並非全部數據咱們都指望編碼到JSON中暴露給外部接口的,因此針對一些敏感的字段咱們每每但願將其從編碼後的JSON數據中忽略掉。那麼上面也說了只有導出的結構體成員纔會被編碼,有的同窗會問我直接用小寫的字段名不行嗎?但是未導出字段只能在包內訪問,像這種攜帶內部敏感數據的每每都是應用的基礎數據,由項目的公共包來提供的。那麼怎麼既能維持字段的導出性又能讓其在JSON數據中被忽略掉呢? 仍是使用結構體的標籤進行註解,好比下面定義的結構體,能夠把身份證IdCard字段在JSON數據中去掉:函數

type User struct {
    Name    string  `json:"name"`
    Age     Int     `json:"int"`
    IdCard  string  `json:"-"`
}

encoding/json的源碼中和文檔中都列舉了經過結構體字段標籤控制數據JSON編碼行爲的說明:this

// Field is ignored by this package.
Field int `json:"-"`

// Field appears in JSON as key "myName".
Field int `json:"myName"`

// Field appears in JSON as key "myName" and
// the field is omitted from the object if its value is empty,
// as defined above.
Field int `json:"myName,omitempty"`

// Field appears in JSON as key "Field" (the default), but
// the field is skipped if empty.
// Note the leading comma.
Field int `json:",omitempty"`

omitempty這個是字段的數據爲空時,在JSON中省略這個字段。爲的是節省數據空間,Protobuf編譯器生成的結構體代碼中每一個字段標籤中都有omitempty。可是在Api開發中這個不經常使用,由於字段不固定對前端很不友好。編碼

Protobuf不瞭解的能夠看我以前寫的文章《Protobuf語言指南》。spa

結構體字段標籤的json註解中都不加omitempty後還遇到一種狀況,就是數據類型爲切片的字段在數據爲空的時候會被JSON編碼爲null而不是[]。這個前端常常會問我沒數據的時候能不能不要返回null,每回還要多寫一個判斷。個人說辭都是不能,其實規範點講是應該返回[]的知識我是我本身沒找到到解決方法。做爲一個在寫代碼上有強迫症的人,這個問題仍是想搞明白的,好在有一天在StackOverflow上看到一個答案,才發現是編碼的疏忽致使的。code

解決空切片在JSON裏被編碼成null

由於切片的零值爲nil,無指向內存的地址,因此當以這種形式定義var f []int初始化slice後,在JSON中將其編碼爲null,若是想在 JSON 中將空 slice 編碼爲[]則需用make初始化 slice爲其分配內存地址:

運行下面的例子能夠看出兩點的區別:

package main

import (
    "encoding/json"
    "fmt"
)

type Person struct {
    Friends []string
}

func main() {
    var f1 []string
    f2 := make([]string, 0)

    json1, _ := json.Marshal(Person{f1})
    json2, _ := json.Marshal(Person{f2})

    fmt.Printf("%s\n", json1)
    fmt.Printf("%s\n", json2)
}

輸出:

{"Friends":null}
{"Friends":[]}

其實致使這個問題的緣由是Go的append函數(甩鍋),咱們都知道引用類型的變量定義後若是沒初始化他們的值是nil,無指向內存的地址,是沒法直接使用的。可是append函數在給切片追加元素時會判斷切片是否已初始化,沒有的話會幫其初始化分配底層數組。個人習慣是先聲明切片,而後再在下面的循環代碼中向切片追加元素。可是若是循環沒有執行,好比你從數據庫沒查出數據,就會致使對應切片字段在無數據時返回的是nil而後被JSON編碼成了null。因此這個算是一個經驗總結出來的Tip吧在寫代碼時你們必定要注意了。

這就是我在開發時把數據編碼成JSON格式時遇到的三個問題和相應的解決方法。加上以前寫的解析JSON的文章,兩個文章加起來差很少就能彙總平常開發中關於encoding/json庫使用的各類問題了。

掃碼下方二維碼關注公衆號第一時間獲取有價值的技術原創文章。

相關文章
相關標籤/搜索