Golang中使用JSON時(shí)區(qū)分空字段和未設(shè)置字段的方法

幾周前, 我在使用 Golang 微服務(wù), 需要添加使用 JSON 數(shù)據(jù)的 CURP 操作的支持. 通常, 我會(huì)為實(shí)體創(chuàng)建一個(gè)結(jié)構(gòu)體, 該結(jié)構(gòu)體中定義了所有字段以及 'omitempty' 屬性, 如下所示

type?Article?struct?{

Id???string??????`json:"id"`

Name?string??????`json:"name,omitempty"`

Desc?string??????`json:"desc,omitempty"`

}

問(wèn)題

但是這種表示形式帶來(lái)了嚴(yán)重的問(wèn)題, 尤其對(duì)于 Update 或 Edit 操作而言.

例如, 假設(shè)更新請(qǐng)求的 JSON 數(shù)據(jù)看起來(lái)像是這樣

{"id":"1234","name":"xyz","desc":""}

注意為空的 desc 字段. 現(xiàn)在讓我們來(lái)看看這段請(qǐng)求數(shù)據(jù)在 Go 中解封后是怎么樣的

func?Test_JSON1(t?*testing.T)?{

jsonData:=`{"id":"1234","name":"xyz","desc":""}`

req:=Article{}

_=json.Unmarshal([]byte(jsonData),&req)

fmt.Printf("%+v",req)

}Output:

===?RUN???Test_JSON1

{Id:1234?Name:xyz?Desc:}

這里的描述是一個(gè)空字符串, 很明顯客戶端希望將 desc 設(shè)置為空字符串, 這是由我們的程序推斷出來(lái)的.

但是, 如果客戶端不希望更改 Desc 的現(xiàn)有值, 在這種情況下, 再次發(fā)送一個(gè)描述字符串是不正確的, 因此請(qǐng)求的 JSON 數(shù)據(jù)可能看起來(lái)像是這樣

{"id":"1234","name":"xyz"}

我們解封到我們的結(jié)構(gòu)體中

func?Test_JSON2(t?*testing.T)?{

jsonData:=`{"id":"1234","name":"xyz"}`

req:=Article{}

_=json.Unmarshal([]byte(jsonData),&req)

fmt.Printf("%+v",req)

}Output:

===?RUN???Test_JSON2

{Id:1234?Name:xyz?Desc:}

額, 仍然會(huì)將 Desc 作為空字符串獲取, 那么如何區(qū)分未設(shè)置字段和空字段

簡(jiǎn)答? 指針

解決辦法

受到一些現(xiàn)有 Golang 庫(kù)的啟發(fā), 如?go-github. 我們可以將結(jié)構(gòu)體字段更改為指針類型, 如下所示

type?Article?struct?{

Id????string??????`json:"id"`

Name?*string??????`json:"name,omitempty"`

Desc?*string??????`json:"desc,omitempty"`

}

通過(guò)這樣做, 我們?cè)谧侄沃刑砑恿祟~外的狀態(tài). 如果原始 JSON 中不存在該字段, 則結(jié)構(gòu)體字段將為空 (nil).

另一方面, 如果該字段確實(shí)存在并且為空, 則指針不為空, 并且該字段包含空值.

注意?- 我沒(méi)有將 'Id' 字段更改為指針類型, 因?yàn)樗痪邆淇諣顟B(tài), id 是必需的, 類似數(shù)據(jù)庫(kù)中的 id.

我們?cè)賴L試一下.

func?Test_JSON_Empty(t?*testing.T)?{

jsonData?:=?`{"id":"1234","name":"xyz","desc":""}`

req?:=?Article{}

_?=?json.Unmarshal([]byte(jsonData),?&req)

fmt.Printf("%+v\n",?req)

fmt.Printf("%s\n",?*req.Name)

fmt.Printf("%s\n",?*req.Desc)

}

func?Test_JSON_Nil(t?*testing.T)?{

jsonData?:=?`{"id":"1234","name":"xyz"}`

req?:=?Article{}

_?=?json.Unmarshal([]byte(jsonData),?&req)

fmt.Printf("%+v\n",?req)

fmt.Printf("%s\n",?*req.Name)

}

Output

===?RUN???Test_JSON_Empty

{Id:1234?Name:0xc000088540?Desc:0xc000088550}

Name:?xyz

Desc:

---?PASS:?Test_JSON_Empty?(0.00s)===?RUN???Test_JSON_Nil

{Id:1234?Name:0xc00005c590?Desc:}

Name:?xyz

---?PASS:?Test_JSON_Nil?(0.00s)

第一種情況, 由于 desc 設(shè)置為空字符串, 因此我們?cè)?Desc?獲得了一個(gè)非空指針并包含一個(gè)空字符串的值. 第二種情況, 該字段未設(shè)置, 我們得到了一個(gè)空字符串指針.

因此我們能夠區(qū)分兩種更新. 這種方式不僅適用于字符串, 而且適用于其他的所有數(shù)據(jù)類型, 包括整型, 嵌套結(jié)構(gòu)體, 等.

但是這種方法也存在一些問(wèn)題.

空安全性: 非指針數(shù)據(jù)類型具備固有的空安全性. 在 Golang 中這意味著字符串或整型永遠(yuǎn)不能為空. 他們始終具備默認(rèn)值. 但是如果定義了指針, 則這些數(shù)據(jù)類型在未手動(dòng)設(shè)置的情況下默認(rèn)為空. 因此, 嘗試在不驗(yàn)證可空性的情況下訪問(wèn)那些指針的數(shù)據(jù)可能會(huì)導(dǎo)致應(yīng)用程序崩潰.

#?以下代碼將崩潰,?因?yàn)?desc?為空

func?Test_JSON_Nil(t?*testing.T)?{

jsonData?:=?`{"id":"1234","name":"xyz"}`

req?:=?Article{}

_?=?json.Unmarshal([]byte(jsonData),?&req)

fmt.Printf("%+v\n",?req)

fmt.Printf("%s\n",?*req.Desc)

}

通過(guò)始終檢查空指針可以很容易的解決此問(wèn)題, 但你的代碼可能會(huì)看起來(lái)會(huì)很啰嗦.

可打印性: 如在基于指針的解決方案的輸出中你可能已經(jīng)注意到的問(wèn)題, 不會(huì)打印指針的值. 二十打印了指針的十六進(jìn)制值, 這在應(yīng)用程序中沒(méi)什么用. 這也可以通過(guò)重新使用 stringer 接口來(lái)克服.

func?(a?*Article)?String()?string?{

output:=fmt.Sprintf("Id:?%s?",a.Id)

if?a.Name!=nil{

output+=fmt.Sprintf("Name:?'%s'?",*a.Name)

}

if?u.Desc!=nil{

output+=fmt.Sprintf("Desc:?'%s'?",u.Desc)

}

return?output

}

附錄:

解決上述問(wèn)題的另一種方法是使用具有可為空類型的三方庫(kù), 其類型可提供檢查是否為空的方法, 而無(wú)需關(guān)心指針.

github.com/guregu/null

github.com/google/go-github

本文來(lái)自php中文網(wǎng)的golang教程欄目:https://www.php.cn/be/go/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容