1. 程式人生 > 其它 >Golang 中使用 JSON 時如何區分空欄位和未設定欄位?

Golang 中使用 JSON 時如何區分空欄位和未設定欄位?

文章目錄

簡介

原文參見:Golang 中使用 JSON 時如何區分空欄位和未設定欄位?
幾周前,我在使用 Golang 微服務,需要新增使用 JSON 資料的 CURP 操作的支援。通常,我會為實體建立一個結構體,該結構體中定義了所有欄位以及 ‘omitempty’ 屬性,如下所示

type Article struct {
   Id   string      `json:"id"`
   Name string      `json:"name,omitempty"`
   Desc string      `json:"desc,omitempty"`
}

問題

但是這種表示形式帶來了嚴重的問題,尤其對於 Update 或 Edit 操作而言.

例如,假設更新請求的 JSON 資料看起來像是這樣

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

注意為空的 desc 欄位。現在讓我們來看看這段請求資料在 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:}

這裡的描述是一個空字串,很明顯客戶端希望將 desc 設定為空字串,這是由我們的程式推斷出來的.

但是,如果客戶端不希望更改 Desc 的現有值,在這種情況下,再次傳送一個描述字串是不正確的,因此請求的 JSON 資料可能看起來像是這樣

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

我們解封到我們的結構體中

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:}

額,仍然會將 Desc 作為空字串獲取,那麼如何區分未設定欄位和空欄位

簡答?指標

解決辦法

受到一些現有 Golang 庫的啟發,如 go-github. 我們可以將結構體欄位更改為指標型別,如下所示

type Article struct {
   Id    string      `json:"id"`
   Name *string      `json:"name,omitempty"`
   Desc *string      `json:"desc,omitempty"`
}

通過這樣做,我們在欄位中添加了額外的狀態。如果原始 JSON 中不存在該欄位,則結構體欄位將為空 (nil).

另一方面,如果該欄位確實存在並且為空,則指標不為空,並且該欄位包含空值.

注意 - 我沒有將 ‘Id’ 欄位更改為指標型別,因為它不具備空狀態,id 是必需的,類似資料庫中的 id.

我們再嘗試一下.

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:<nil>}
Name: xyz
--- PASS: Test_JSON_Nil (0.00s)

第一種情況,由於 desc 設定為空字串,因此我們在 Desc 獲得了一個非空指標幷包含一個空字串的值。第二種情況,該欄位未設定,我們得到了一個空字串指標.

因此我們能夠區分兩種更新。這種方式不僅適用於字串,而且適用於其他的所有資料型別,包括整型,巢狀結構體,等.

但是這種方法也存在一些問題.

空安全性: 非指標資料型別具備固有的空安全性。在 Golang 中這意味著字串或整型永遠不能為空。他們始終具備預設值。但是如果定義了指標,則這些資料型別在未手動設定的情況下預設為空。因此,嘗試在不驗證可空性的情況下訪問那些指標的資料可能會導致應用程式崩潰.

# 以下程式碼將崩潰, 因為 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)
}

通過始終檢查空指標可以很容易的解決此問題,但你的程式碼可能會看起來會很囉嗦.

可列印性: 如在基於指標的解決方案的輸出中你可能已經注意到的問題,不會列印指標的值。二十列印了指標的十六進位制值,這在應用程式中沒什麼用。這也可以通過重新使用 stringer 介面來克服.

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
}

附錄:

解決上述問題的另一種方法是使用具有可為空型別的三方庫,其型別可提供檢查是否為空的方法,而無需關心指標.
github.com/guregu/null
github.com/google/go-github

————————————————
原文作者:wangchunbo
轉自連結:https://learnku.com/go/t/49332
版權宣告:著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請保留以上作者資訊和原文連結。