【Go Web開發(fā)】定制JSON序列化

高級JSON定制化

通過使用結構體標簽、添加空白和封裝響應數據氛改,我們已經能夠為JSON響應添加大量定制信息。但是撩独,當這些內容還不夠時敞曹,您需要更自由地定制JSON時账月,會發(fā)生什么呢?

要回答這個問題,我們首先需要談談Go如何處理JSON序列化的一些理論澳迫。要理解的關鍵是:

Go是在什么時候將特殊類型序列化為JSON局齿,它首先查看對應的類型是否實現了MarshalJSON()方法。如果實現了橄登,GO將調用這個方法來決定JSON編碼格式抓歼。

這么講有點模糊,我們更精確點拢锹。嚴格地說谣妻,當Go將特定類型編碼為JSON時,它會查看該類型是否滿足json.Marshaler接口卒稳,該接口如下所示:

type Marshaler interface {
    MarshalJSON() ([]byte, error)
}

如果類型確實滿足接口蹋半,那么Go將調用它的MarshalJSON()方法,并使用它返回的[]byte切片作為JSON編碼的值充坑。

如果該類型沒有MarshalJSON()方法减江,那么Go將返回嘗試根據自己的內部規(guī)則將其編碼為JSON。

因此捻爷,如果我們想定制某些類型的編碼方式辈灼,只需要在其上實現MarshalJSON()方法,該方法以[]byte類型返回自定義的JSON內容也榄。

提示:如果您查看time.Time類型源代碼巡莹,就可以看到這一點。time.Time實際上是一個結構體手蝎,但是它有一個MarshalJSON()方法榕莺,輸出RFC3339格式JSON對象。當time.Time值被序列化為JSON對象時棵介,就會調用MarshalJSON()方法钉鸯。

定制電影Runtime字段JSON序列化

為了說明這一點,讓我們看一下應用程序中的一個具體示例邮辽。

當我們的Movie結構被編碼為JSON時唠雕,Runtime字段(它是一個int32類型)編碼為JSON數字。現在我們來更改它吨述,將其編碼為"<runtime> mins“的字符串岩睁。像這樣:

{
    "id": 123,
    "title": "Casablanca",
    "runtime": "102 mins",
    "genres":
    [
        "drama",
        "romance",
        "war"
    ],
    "version": 1
}

有幾種方法可以實現這一點,但一種簡單的方法是為Runtime字段創(chuàng)建一個自定義類型揣云,并在這個類型上實現MarshalJSON()方法捕儒。

為了防止internal/data/movie.go文件不會太亂,我們創(chuàng)建一個新的文件來處理runtime類型序列化邏輯:

 $ touch internal/data/runtime.go

然后繼續(xù)添加以下代碼:

package data

import (
    "fmt"
    "strconv"
)

//申明Runtime類型,其底層是int32類型(和movie中的字段一樣)
type Runtime int32

//實現MarshalJSON()方法刘莹,這樣就實現了json.Marshaler接口阎毅。
func (r Runtime) MarshalJSON() ([]byte, error) {
    //生成一個字符串包含電影時長
    jsonValue := fmt.Sprintf("%d mins", r)

    //使用strconv.Quote()函數封裝雙引號。為了在JSON中以字符串對象輸出点弯,需要用雙引號扇调。
    quotedJSONValue := strconv.Quote(jsonValue)
    //將字符串轉為[]byte返回
    return []byte(quotedJSONValue)
}

這里我想強調兩點:

  • 如果您的MarshalJSON()方法像我們的方法一樣返回一個JSON字符串值,那么您必須在返回字符串之前用雙引號包裝它抢肛。否則它將不會被解釋為JSON字符串狼钮,你將收到類似于這樣的運行時錯誤:
    json: error calling MarshalJSON for type data.Runtime: invalid character 'm' after top-level value
    
  • 我們故意為MarshalJSON()方法使用值接收器,而不是指針接收器func (r *Runtime) MarshalJSON()捡絮。這給了我們更多的靈活性熬芜,因為這意味著定制JSON編碼將對Runtime值對象和指針對象都有效。正如Effective Go提到的:

如果你不確定指針和值接收器之間的區(qū)別锦援,那么這篇博客提供了一個很好的總結猛蔽。

好的,現在有了自定義Runtime類型灵寺,打開internal/data/movies.go文件并更新Movie結構:

File: internal/data/movies.go


package data

import (
    "time"
)

type Movie struct {
    ID       int64     `json:"id"`
    CreateAt time.Time `json:"-"`
    Title    string    `json:"title"`
    Year     int32     `json:"year,omitempty"`
        //使用Runtime類型取代int32曼库,注意omitempty還是能生效的
    Runtime  Runtime   `json:"runtime,omitempty,string"`
    Genres   []string  `json:"genres,omitempty"`
    Version  int32     `json:"version"`
}

重啟服務然后對GET /v1/movies/:id接口發(fā)起請求。你應該看到一個包含自定義runtime值的響應略板,格式為"xx mins"毁枯,類似如下:

$ curl localhost:4000/v1/movies/123
{
    "movie":
    {
        "id": 123,
        "title": "Casablanca",
        "runtime": "102 mins",
        "genres":
        [
            "drama",
            "romance",
            "war"
        ],
        "version": 1
    }
}

總之,這是定制JSON序列化的一種很好的方法叮称。我們的代碼簡潔明了种玛,并且我們有一個自定義的Runtime類型,可以隨時隨地使用它瓤檐。

但也有不利的一面赂韵。在將代碼與其他包集成時,使用自定義類型有時會很尷尬挠蛉,您可能需要執(zhí)行類型轉換祭示,將自定義類型轉換為其他包理解和可接受的值。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末谴古,一起剝皮案震驚了整個濱河市质涛,隨后出現的幾起案子,更是在濱河造成了極大的恐慌掰担,老刑警劉巖汇陆,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異带饱,居然都是意外死亡毡代,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來月趟,“玉大人灯蝴,你說我怎么就攤上這事⌒⒆冢” “怎么了?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵耕肩,是天一觀的道長因妇。 經常有香客問我,道長猿诸,這世上最難降的妖魔是什么婚被? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮梳虽,結果婚禮上址芯,老公的妹妹穿的比我還像新娘。我一直安慰自己窜觉,他們只是感情好谷炸,可當我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著禀挫,像睡著了一般旬陡。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上语婴,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天描孟,我揣著相機與錄音,去河邊找鬼砰左。 笑死匿醒,一個胖子當著我的面吹牛,可吹牛的內容都是我干的缠导。 我是一名探鬼主播廉羔,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼酬核!你這毒婦竟也來了蜜另?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤嫡意,失蹤者是張志新(化名)和其女友劉穎举瑰,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體蔬螟,經...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡此迅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片耸序。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡忍些,死狀恐怖,靈堂內的尸體忽然破棺而出坎怪,到底是詐尸還是另有隱情罢坝,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布搅窿,位于F島的核電站嘁酿,受9級特大地震影響,放射性物質發(fā)生泄漏男应。R本人自食惡果不足惜闹司,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望沐飘。 院中可真熱鬧游桩,春花似錦、人聲如沸耐朴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽隔箍。三九已至谓娃,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蜒滩,已是汗流浹背滨达。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留俯艰,地道東北人捡遍。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像竹握,于是被迫代替她去往敵國和親画株。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,512評論 2 359

推薦閱讀更多精彩內容