Go 中使用 JSON 时,如何区分空字段和未设置字段

简介: Go 中使用 JSON 时,如何区分空字段和未设置字段

几周前,我正在开发一个基于 Golang 的微服务项目,需要在其中添加对 JSON 数据的 CRUD 支持。

通常,我会为实体构建一个结构,其中包含定义的所有字段以及 "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 设置为空字符串,这是由我们的程序推断出来的。

但是,如果客户端不希望更改 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 数据中不存在,那么字段将会为 null(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)
}


输出

=== 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 被设置成空字符串,我们得到一个非空指针,它的值是空字符串。在第二种情况下,如果字段没有被设置,我们得到一个空指针。

因此,我们就能够区分这两种更新了。这种方法不仅适用于字符串类型,还适用于其他类型,包括 int、嵌套 struct 等。


但是这种方法会带来一些问题。


空安全:非指针数据类型具有固有的空安全。这更确切的说,一个 string 或者 int 在 Golang 中永远不会为 null。它们总是有一个默认值。但是,一旦定义了指针,如果没有手动设置,那么这些数据类型默认为 null。因此,尝试访问这些指针数据而不验证空性的情况下可能会导致应用程序崩溃。

#The following code will crash because desc is null
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 a.Desc!=nil{
   output+=fmt.Sprintf("Desc: '%s' ",a.Desc)
   }
   return output
}

附录


相关文章
|
1月前
|
JSON JavaScript 前端开发
Go语言中json序列化的一个小坑,建议多留意一下
在Go语言开发中,JSON因其简洁和广泛的兼容性而常用于数据交换,但其在处理数字类型时存在精度问题。本文探讨了JSON序列化的一些局限性,并介绍了两种替代方案:Go特有的gob二进制协议,以及msgpack,两者都能有效解决类型保持和性能优化的问题。
54 7
|
1月前
|
JSON 前端开发 JavaScript
聊聊 Go 语言中的 JSON 序列化与 js 前端交互类型失真问题
在Web开发中,后端与前端的数据交换常使用JSON格式,但JavaScript的数字类型仅能安全处理-2^53到2^53间的整数,超出此范围会导致精度丢失。本文通过Go语言的`encoding/json`包,介绍如何通过将大整数以字符串形式序列化和反序列化,有效解决这一问题,确保前后端数据交换的准确性。
50 4
|
1月前
|
前端开发 开发者
如何理解 package.json 中的 proxy 字段?
`package.json` 中的 `proxy` 字段用于配置代理服务器,帮助前端开发中解决跨域问题及模拟后端响应。其基本概念、使用场景及配置方法将在本文中详细探讨,助力开发者高效调试与测试。
67 4
|
1月前
|
安全 JavaScript
如何在`package.json`中正确设置依赖版本范围?
正确设置 `package.json` 中的依赖版本范围需要综合考虑项目的需求、依赖库的稳定性和兼容性,以及开发和维护的便利性等因素。通过合理选择版本范围符号,并结合定期的审查和测试,可以有效地管理项目依赖,确保项目的稳定运行。
52 1
|
1月前
|
JSON Java 数据格式
springboot中表字段映射中设置JSON格式字段映射
springboot中表字段映射中设置JSON格式字段映射
144 1
|
2月前
|
JSON API 数据格式
postman如何发送json请求其中file字段是一个图片
postman如何发送json请求其中file字段是一个图片
168 4
|
3月前
|
JSON 数据库 数据格式
数据库表如果有json字段,该怎么更新
数据库表如果有json字段,该怎么更新
|
4月前
|
JSON 人工智能 编译器
Go json 能否解码到一个 interface 类型的值
Go json 能否解码到一个 interface 类型的值
39 1
|
4月前
|
JSON Go 数据格式
Go - json.Unmarshal 遇到的小坑
Go - json.Unmarshal 遇到的小坑
78 9
|
4月前
|
JSON Go 数据格式
Go实现json字符串与各类struct相互转换
文章通过Go语言示例代码详细演示了如何实现JSON字符串与各类struct之间的相互转换,包括结构体对象生成JSON字符串和JSON字符串映射到struct对象的过程。
38 0