【Go Web開發(fā)】自定義JSON解碼

在本系列文章前面,我們在API中添加了一些自定義的JSON編碼處理借跪,以便在JSON響應(yīng)中以“(runtime) mins”的格式顯示電影時長信息政己。

在本章,我們將從另一方面考慮掏愁,如何將“(runtime) mins”字段解析到int32類型的Go結(jié)構(gòu)體字段中歇由,也稱為反序列化處理。

如果你現(xiàn)在嘗試用"(runtime) mins"這種格式發(fā)送一個請求果港,會得到一個400 Bad request響應(yīng)(因?yàn)椴豢赡軐SON字符串("107 mins")解碼為int32類型)沦泌。如下所示:

$ curl -d '{"title": "Moana", "runtime": "107 mins"}' localhost:4000/v1/movies
{
    "error": "body contains incorrect JSON type for \"runtime\""
}

為了使其正常工作,我們需要攔截JSON解碼過程辛掠,并手動將“<runtime> mins”JSON字符串轉(zhuǎn)換為int32谢谦。

因此我們該如何處理呢?

json.Unmarshaler接口

這里的關(guān)鍵是了解Go的json.Unmarshaler接口萝衩,它看起來像這樣:

type Unmarshaler interface { 
    UnmarshalJSON([]byte) error
}

當(dāng)Go解碼JSON時回挽,會檢查目標(biāo)字段的類型是否實(shí)現(xiàn)了json.Unmarshaler接口。如果實(shí)現(xiàn)了該接口猩谊,Go將調(diào)用UnmarshalJSON()函數(shù)來決定如何將JSON解碼到目標(biāo)類型千劈。這剛好與json.Marshaler接口相反,我們之前使用它來定制JSON編碼行為预柒。

我們看看如何實(shí)現(xiàn)這個接口队塘。

首先我們需要做的第一件事是更新createMovieHandler,以便輸入結(jié)構(gòu)體使用我們自定義的Runtime類型宜鸯,而不是常規(guī)的int32。你應(yīng)該記得遮怜,我們的runtime類型仍然是基礎(chǔ)類型int32淋袖,但通過自定義類型,我們可以自由地讓它上實(shí)現(xiàn)UnmarshalJSON()方法锯梁。

下面更新接口處理程序如下:

File:cmd/api/movies.go


package main

...

func (app *application) createMovieHandler(w http.ResponseWriter, r *http.Request) {
    var input struct {
        Title   string       `json:"title"`
        Year    int32        `json:"year"`
        Runtime data.Runtime `json:"runtime"`
        Genres  []string     `json:"genres"`
    }
    err := app.readJSON(w, r, &input)
    if err != nil {
        app.badRequestResponse(w, r, err)
        return
    }

    fmt.Fprintf(w, "%+v\n", input)
}

然后我們轉(zhuǎn)到internal/data/runtime.go文件即碗,為Runtime類型添加UnmarshalJSON方法焰情。在這個方法里面需要解析“(runtime) mins”格式的JSON字符串,然后將runtime數(shù)值解析為int32剥懒,并將其賦值給Runtime本身内舟。

這實(shí)際上有點(diǎn)復(fù)雜,有一些重要的細(xì)節(jié)初橘,最好直接進(jìn)入代碼验游,并在后面用注釋進(jìn)行解釋。

File:internal/data/runtime.go


package data

import (
    "errors"
    "fmt"
    "strconv"
    "strings"
)

//如果UnmarshalJSON函數(shù)不能將JSON字符串解析為Runtime類型保檐,就返回該錯誤
var ErrInvalidRuntimeFormat = errors.New("invalid runtime format")

type Runtime int32

//Runtime類型實(shí)現(xiàn)UnmarshalJSON()方法耕蝉,這樣就實(shí)現(xiàn)了json.Unmarshaler接口。注意夜只,因?yàn)閁nmarshalJSON()會改變狀態(tài)
//因此需要使用指針接收者垒在。否則就要返回一個實(shí)例的拷貝。
func (r *Runtime) UnmarshalJSON(jsonValue []byte) error {
        //希望接收到的值是一個JSON字符串"(runtime) mins"扔亥,第一步需要去掉雙引號场躯。
    unquotedJSONValue, err := strconv.Unquote(string(jsonValue))
    if err != nil {
        return ErrInvalidRuntimeFormat
    }
        //分割字符串以分離包含數(shù)字的部分
    parts := strings.Split(unquotedJSONValue, " ")
    if len(parts) != 2 || parts[1] != "mins" {
        return ErrInvalidRuntimeFormat
    }
        //將數(shù)字字符串轉(zhuǎn)為int32類型
    i, err := strconv.ParseInt(parts[0], 10, 32)
    if err != nil {
        return ErrInvalidRuntimeFormat
    }
        //將int32強(qiáng)制轉(zhuǎn)為Runtime類型,并賦值給接收者旅挤,注意是指針類型的接收者踢关。
    *r = Runtime(i)
    return nil
}

完成以上代碼后重啟服務(wù),然后使用"(runtime) min"格式的值作為JSON字段谦铃≡懦桑可以看到請求會被正常處理。數(shù)值會從字符串中提取出來賦值給input結(jié)構(gòu)體中的Runtime類型字段驹闰,如下所示:

 $ curl -d '{"title": "Moana", "runtime": "107 mins"}' localhost:4000/v1/movies
{Title:Moana Year:0 Runtime:107 Genres:[]}

然而瘪菌,如果你使用JSON數(shù)字或任何其他格式發(fā)請求,你應(yīng)該會得到一個ErrInvalidRuntimeFormat錯誤消息的響應(yīng)嘹朗,類似如下:

$ curl -d '{"title": "Moana", "runtime": 107}' localhost:4000/v1/movies
{
    "error": "invalid runtime format"
}

$ curl -d '{"title": "Moana", "runtime": "107 minutes"}' localhost:4000/v1/movies
{
    "error": "invalid runtime format"
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末师妙,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子屹培,更是在濱河造成了極大的恐慌默穴,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件褪秀,死亡現(xiàn)場離奇詭異蓄诽,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)媒吗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進(jìn)店門仑氛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事锯岖〗橥啵” “怎么了?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵出吹,是天一觀的道長遇伞。 經(jīng)常有香客問我,道長捶牢,這世上最難降的妖魔是什么鸠珠? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮叫确,結(jié)果婚禮上跳芳,老公的妹妹穿的比我還像新娘。我一直安慰自己竹勉,他們只是感情好飞盆,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著次乓,像睡著了一般吓歇。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上票腰,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天城看,我揣著相機(jī)與錄音,去河邊找鬼杏慰。 笑死测柠,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的缘滥。 我是一名探鬼主播轰胁,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼朝扼!你這毒婦竟也來了赃阀?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤擎颖,失蹤者是張志新(化名)和其女友劉穎榛斯,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體搂捧,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡驮俗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了允跑。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片意述。...
    茶點(diǎn)故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖吮蛹,靈堂內(nèi)的尸體忽然破棺而出荤崇,到底是詐尸還是另有隱情,我是刑警寧澤潮针,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布术荤,位于F島的核電站,受9級特大地震影響每篷,放射性物質(zhì)發(fā)生泄漏瓣戚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一焦读、第九天 我趴在偏房一處隱蔽的房頂上張望子库。 院中可真熱鬧,春花似錦矗晃、人聲如沸仑嗅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽仓技。三九已至,卻和暖如春俗他,著一層夾襖步出監(jiān)牢的瞬間脖捻,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工兆衅, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留地沮,地道東北人。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓羡亩,卻偏偏與公主長得像摩疑,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子夕春,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評論 2 351

推薦閱讀更多精彩內(nèi)容