欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

淺析golang如何處理json中的null

 更新時(shí)間:2023年09月19日 11:17:56   作者:uccs  
json?是一種常用的數(shù)據(jù)格式,在?go?使用?json?序列化和反序列化時(shí)比較方便的,但在使用過(guò)程中,會(huì)遇到一些問(wèn)題,比如?null,所以下面我們就來(lái)看看golang如何處理json中的null吧

最近學(xué)習(xí) go 發(fā)現(xiàn)發(fā)現(xiàn)處理 json 中的 null 時(shí),會(huì)這么難受,需要專門寫一篇文章來(lái)講解一下

以下是正文

json 是一種常用的數(shù)據(jù)格式,在 go 使用 json 序列化和反序列化時(shí)比較方便的,但在使用過(guò)程中,會(huì)遇到一些問(wèn)題,比如 null

由于 go 沒(méi)有聯(lián)合類型,當(dāng) json 中有個(gè)屬性為 null 時(shí),就無(wú)法直接將 null 轉(zhuǎn)換成 nil 后賦值給某個(gè)具體的類型

比如下面這個(gè)例子:

Name 定一個(gè)的是 string 類型,但在 jsonname 的值為 null,直接轉(zhuǎn)換會(huì)報(bào)錯(cuò)

type Tag struct {
  ID   int    `json:"id"`
  Name string `json:"name"`
}
tag := Tag{
  ID:   1,
  Name: nil,  // 這里會(huì)報(bào)錯(cuò)
}

這種問(wèn)題不光出現(xiàn)在 json 解析時(shí),還會(huì)出現(xiàn)在數(shù)據(jù)庫(kù)讀寫時(shí)

比如在數(shù)據(jù)庫(kù)中,某個(gè)字段的值為 NULL,在讀取時(shí),會(huì)被解析成 nil,但是 go 中的類型是不能直接賦值為 nil

所以在這兩種場(chǎng)景下該怎么解決呢?

一般有三種方法:

  • 使用指針
  • 自定義類型
  • 使用第三方庫(kù)

使用指針

go 的指針類型是可以賦值為 nil 的,所以我們使用指針解決這個(gè)問(wèn)題

我們把上面例子中的 Name 定義為 string 的指針類型,如下代碼:

type Tag struct {
  ID   int    `json:"id"`
  Name *string `json:"name"`
}
name := "uccs" // 定義一個(gè) string 類型的變量,因?yàn)椴荒馨岩粋€(gè)字面量直接賦值給指針類型
tag := Tag{
  ID:   1,
  Name: &name,  // 將 name 的地址賦值給 Name,使用 & 地址符
}

在使用時(shí),需要先判斷一下 Name 是為 nil,如果不為 nil,則使用 * 取值符取出值

// Name 是指針類型,判斷是否為 nil 時(shí)不需要使用 * 取值符
if tag.Name != nil {
  // Name 是指針類型,取值時(shí)需要使用 * 取值符
  if *tag.Name == "uccs" {
    // ...
  }
}

注意事項(xiàng)

ORM 框架會(huì)實(shí)現(xiàn)一個(gè) NullString 的類型,

當(dāng)我們?cè)诙x Model 時(shí),如果某個(gè)字段可以為 NULL,則 ORM 框架會(huì)把它定義為 NullString 類型(下文講解)

給指針賦值時(shí),不能直接使用字面量,需要先定義一個(gè)變量,然后將變量的地址賦值給指針

使用指針時(shí)需要注意,這里會(huì)比較繞

在判斷是否為 nil 時(shí),不需要使用 * 取值符

在判斷是否為 uccs 時(shí),需要使用 * 取值符

當(dāng)遇到 panic: runtime error: invalid memory address or nil pointer dereference 錯(cuò)誤時(shí),說(shuō)明指針為 nil

也就是說(shuō)使用指針時(shí),我們最需要注意的是:在指針上取值時(shí),一定要注意它是不是為 nil

自定義類型

我們使用結(jié)構(gòu)體定義一個(gè)類型:NullString,它有兩個(gè)屬性 StringValid

String 用來(lái)存儲(chǔ)字符串

Valid 用來(lái)標(biāo)識(shí) String 是否有值

  • 如果 Validtrue,則 String 有值
  • 如果 Validfalse,則 String 是空值 ""
type NullString struct {
  String string
  Valid  bool
}

當(dāng)我們定義好類型后,需要考考慮兩個(gè)問(wèn)題:

  • 如何解決 json 解析時(shí) null 的問(wèn)題
  • 如何向數(shù)據(jù)庫(kù)進(jìn)行讀寫

go 有個(gè)特點(diǎn),你自定義的類型有某些方法,那么在某些場(chǎng)景下,這些方法會(huì)被調(diào)用

比如,序列化時(shí),會(huì)調(diào)用 MarshalJSON 方法,反序列化時(shí),會(huì)調(diào)用 UnmarshalJSON 方法

你的自定義類型實(shí)現(xiàn)了這兩個(gè)方法,那么在序列化和反序列化時(shí),這兩個(gè)方法就會(huì)被調(diào)用

數(shù)據(jù)庫(kù)讀寫是實(shí)現(xiàn) ScanValue 方法

所以下面就從這兩塊講起:

序列化和反序列化

我們給 NullString 類型添加兩個(gè)方法 MarshalJSONUnmarshalJSON

// 序列化時(shí)
func (ns NullString) MarshalJSON() ([]byte, error) {
  // 如果 Valid 為 true,則返回 String 的 json 序列化結(jié)果
  if ns.Valid {
    return []byte(`"` + ns.String + `"`), nil
  }
  // 如果 Valid 為 false,則返回 null 序列化的結(jié)果
  return []byte("null"), nil
}
// 反序列化
func (ns *NullString) UnmarshalJSON(data []byte) error {
  // 如果 data 為 null,則 Valid 為 false
  // String 為空字符串
  if string(data) == "null" {
    ns.String, ns.Valid = "", false
    return nil
  }
  // 否則,將 data 反序列化到 String 中
  // 并將 Valid 設(shè)置為 true
  if err := json.Unmarshal(data, &ns.String); err != nil {
    return err
  }
  ns.Valid = true
  return nil
}

有了這兩個(gè)方法之后,我們就解決了 json 解析時(shí) null 的問(wèn)題

是什么時(shí)候會(huì)觸發(fā)這兩個(gè)方法呢?

json 內(nèi)容解析填充 struct 的場(chǎng)景時(shí)會(huì)觸發(fā) UnmarshalJSON 的調(diào)用

  • 直接調(diào)用 json.Unmarshal 對(duì) json 數(shù)據(jù)進(jìn)行解析時(shí)
  • http.Request 讀取 json Body 時(shí)
  • 使用 encoding/jsonDecoder 進(jìn)行解碼時(shí)
  • 對(duì)實(shí)現(xiàn)了 Unmarshaler 接口的對(duì)象調(diào)用 UnmarshalJSON 方法時(shí)

反過(guò)來(lái),將 struct 內(nèi)容序列化為 json 時(shí)會(huì)觸發(fā) json.Marshal 的調(diào)用

  • 直接調(diào)用 json.Marshal 對(duì)一個(gè)對(duì)象進(jìn)行編碼
  • 使用 http.ResponseWriterWrite 方法響應(yīng) json 數(shù)據(jù)時(shí)
  • 使用 encoding/jsonEncoder 進(jìn)行編碼時(shí)
  • 對(duì)實(shí)現(xiàn)了 Marshaler 接口的對(duì)象調(diào)用 MarshalJSON 方法時(shí)

序列化和反序列化問(wèn)題解決了,那如何向數(shù)據(jù)庫(kù)進(jìn)行讀寫呢?

數(shù)據(jù)庫(kù)讀寫

我們?cè)俳o NullString 添加兩個(gè)方法 ValueScan

  • Value 方法會(huì)在寫入數(shù)據(jù)庫(kù)時(shí)被調(diào)用
  • Scan 方法會(huì)在從數(shù)據(jù)庫(kù)讀取時(shí)被調(diào)用
// Scan 方法在 數(shù)據(jù)庫(kù)讀取時(shí)被調(diào)用
func (ns *NullString) Scan(value interface{}) error {
  // 如果 value 為 nil,則 Valid 為 false,String 為空字符串
  if value == nil {
    ns.String, ns.Valid = "", false
    return nil
  }
  // 否則,將 value 斷言為 string 類型,斷言成功 Valid 為 true,String 為 value
  ns.String, ns.Valid = value.(string)
  return nil
}
// Value 方法 在寫入數(shù)據(jù)庫(kù)時(shí)被調(diào)用
func (ns NullString) Value() (driver.Value, error) {
  // 如果 Valid 為 false,則返回 nil
  if !ns.Valid {
    return nil, nil
  }
  // 否則,返回 String
  return ns.String, nil
}

添加這兩個(gè)方法后,我們就可以向數(shù)據(jù)庫(kù)中寫入 null

是什么時(shí)候會(huì)觸發(fā)這兩個(gè)方法呢?

Scanner 接口的 Scan 方法會(huì)在以下情況被調(diào)用

ORM 框架如 GORM、database/sql 等查詢時(shí),掃描結(jié)果到自定義模型

Valuer 接口的 Value 方法會(huì)在以下情況被調(diào)用

ORM 框架如 GORM、database/sql 構(gòu)造寫入語(yǔ)句時(shí),獲取自定義模型的值

使用

將上面 Tag 的解構(gòu)體改為:

type Tag struct {
  ID   int        `json:"id"`
  Name NullString `json:"name"`
}

不過(guò)這里要注意的一點(diǎn)是,在給 Name 賦值時(shí),需要使用 NullString 進(jìn)行賦值,如果下所示:

tag := Tag{
  ID:   1,
  Name: NullString{String: "hello", Valid: true},
}

最后需要注意的是,go 中其他類型也要實(shí)現(xiàn)這樣的方法,比如 NullIntNullBool 等,可以參照這個(gè) guregu/null 這個(gè)庫(kù)

使用第三方庫(kù)

第三方庫(kù) guregu/null 已經(jīng)實(shí)現(xiàn)了上面的方法,我們可以直接使用

ORM 一般都實(shí)現(xiàn)了這些功能

需要注意的是有些 ORM 只實(shí)現(xiàn)了 ScannerValuer 接口,沒(méi)有實(shí)現(xiàn) MarshalJSONUnmarshalJSON 接口

總結(jié)

  • 使用 string 只能滿足必填的情況
  • ORM 框架一般都實(shí)現(xiàn)了 ScannerValuer 接口,但是有些 ORM 沒(méi)有實(shí)現(xiàn) MarshalJSONUnmarshalJSON 接口,需要自己實(shí)現(xiàn),或者使用第三方庫(kù)
  • 使用指針時(shí),如 *string,需要注意指針是否為 nil

到此這篇關(guān)于淺析golang如何處理json中的null的文章就介紹到這了,更多相關(guān)go處理json內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 基于Golang開(kāi)發(fā)一個(gè)輕量級(jí)登錄庫(kù)/框架

    基于Golang開(kāi)發(fā)一個(gè)輕量級(jí)登錄庫(kù)/框架

    幾乎每個(gè)項(xiàng)目都會(huì)有登錄,退出等用戶功能,而登錄又不單僅僅是登錄,我們要考慮很多東西。所以本文就來(lái)用Golang開(kāi)發(fā)一個(gè)輕量級(jí)登錄庫(kù)/框架吧
    2023-05-05
  • golang開(kāi)發(fā)中channel使用

    golang開(kāi)發(fā)中channel使用

    channel[通道]是golang的一種重要特性,正是因?yàn)閏hannel的存在才使得golang不同于其它語(yǔ)言。這篇文章主要介紹了golang開(kāi)發(fā)中channel使用,需要的朋友可以參考下
    2020-09-09
  • 使用GORM將PostgreSQL集成到Go框架中

    使用GORM將PostgreSQL集成到Go框架中

    在go中集成postgresql需使用gorm orm,步驟如下:安裝go和postgresql,安裝 gorm:go get -u gorm.io/gorm,配置數(shù)據(jù)庫(kù)連接字符串,定義模型類,遷移數(shù)據(jù)庫(kù)架構(gòu),使用 gorm 進(jìn)行增刪改查操作,本指南將介紹如何使用 GORM(一個(gè)廣受歡迎的 ORM),將PostgreSQL集成到你的Go應(yīng)用中
    2024-08-08
  • Go語(yǔ)言中的自定義類型你了解嗎

    Go語(yǔ)言中的自定義類型你了解嗎

    自定義類型是 Go 語(yǔ)言中非常重要的概念之一,通過(guò)自定義類型,我們可以更好地封裝數(shù)據(jù)、組織代碼,提高程序的可讀性和可維護(hù)性。本文將從以下幾個(gè)方面介紹 Go 自定義類型的相關(guān)知識(shí),感興趣的可以了解一下
    2023-04-04
  • golang實(shí)現(xiàn)RPC模塊的示例

    golang實(shí)現(xiàn)RPC模塊的示例

    本文詳細(xì)介紹了在Go語(yǔ)言中如何實(shí)現(xiàn)RPC模塊,包括RPC服務(wù)端和客戶端的構(gòu)建及代碼實(shí)現(xiàn),同時(shí)提到了使用JSON-RPC的方法,通過(guò)簡(jiǎn)單的步驟,可以實(shí)現(xiàn)跨進(jìn)程的遠(yuǎn)程過(guò)程調(diào)用,感興趣的可以了解一下
    2024-09-09
  • go cron定時(shí)任務(wù)的基本使用講解

    go cron定時(shí)任務(wù)的基本使用講解

    這篇文章主要為大家介紹了gocron定時(shí)任務(wù)的基本使用講解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-06-06
  • golang兩種調(diào)用rpc的方法

    golang兩種調(diào)用rpc的方法

    這篇文章主要介紹了golang兩種調(diào)用rpc的方法,結(jié)合實(shí)例形式分析了Go語(yǔ)言調(diào)用rpc的原理與實(shí)現(xiàn)方法,需要的朋友可以參考下
    2016-07-07
  • Go語(yǔ)言select語(yǔ)句用法示例

    Go語(yǔ)言select語(yǔ)句用法示例

    這篇文章主要為大家介紹了Go語(yǔ)言select語(yǔ)句用法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • Go項(xiàng)目中的GOPROXY設(shè)置

    Go項(xiàng)目中的GOPROXY設(shè)置

    GOPROXY是Go語(yǔ)言中用于指定模塊代理服務(wù)器的環(huán)境變量,設(shè)置GOPROXY可以通過(guò)操作系統(tǒng)環(huán)境變量、Go命令行參數(shù)或Go環(huán)境配置文件進(jìn)行,感興趣的可以了解一下
    2024-09-09
  • golang實(shí)現(xiàn)命令行程序的使用幫助功能

    golang實(shí)現(xiàn)命令行程序的使用幫助功能

    這篇文章介紹了golang實(shí)現(xiàn)命令行程序使用幫助的方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-07-07

最新評(píng)論