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
}

附录


相关文章
|
3天前
|
SQL DataWorks 关系型数据库
DataWorks操作报错合集之DataWorks在同步mysql时报错Code:[Framework-02],mysql里面有个json类型字段,是什么原因导致的
DataWorks是阿里云提供的一站式大数据开发与治理平台,支持数据集成、数据开发、数据服务、数据质量管理、数据安全管理等全流程数据处理。在使用DataWorks过程中,可能会遇到各种操作报错。以下是一些常见的报错情况及其可能的原因和解决方法。
18 0
|
3天前
|
JSON 编解码 Go
【Go语言专栏】Go语言中的JSON编码与解码
【4月更文挑战第30天】Go语言内置JSON编码解码支持,简化了数据交换。`json.Marshal`用于将Go结构体转换为JSON,如示例中`Person`结构体的编码。`json.Unmarshal`则将JSON数据反序列化到结构体,需传入结构体变量的地址。错误处理至关重要,特别是在处理大量数据时,要注意性能优化,如避免不必要的转换和重复操作。了解自定义编码解码和最佳实践能提升开发效率。掌握这些技能,有助于构建高效Go应用。
|
4天前
|
分布式计算 DataWorks 关系型数据库
DataWorks产品使用合集之在DataWorks中,使用JSON解析函数将MySQL表中的字段解析成多个字段将这些字段写入到ODPS(MaxCompute)中如何解决
DataWorks作为一站式的数据开发与治理平台,提供了从数据采集、清洗、开发、调度、服务化、质量监控到安全管理的全套解决方案,帮助企业构建高效、规范、安全的大数据处理体系。以下是对DataWorks产品使用合集的概述,涵盖数据处理的各个环节。
13 3
|
4天前
|
存储 JSON DataWorks
DataWorks产品使用合集之DataWorks将 MongoDB 中的数组类型写入到 DataWorks 的单个字段时,表示为字符串格式而非 JSON 格式如何解决
DataWorks作为一站式的数据开发与治理平台,提供了从数据采集、清洗、开发、调度、服务化、质量监控到安全管理的全套解决方案,帮助企业构建高效、规范、安全的大数据处理体系。以下是对DataWorks产品使用合集的概述,涵盖数据处理的各个环节。
13 3
|
4天前
|
JSON JavaScript 前端开发
Golang深入浅出之-Go语言JSON处理:编码与解码实战
【4月更文挑战第26天】本文探讨了Go语言中处理JSON的常见问题及解决策略。通过`json.Marshal`和`json.Unmarshal`进行编码和解码,同时指出结构体标签、时间处理、omitempty使用及数组/切片区别等易错点。建议正确使用结构体标签,自定义处理`time.Time`,明智选择omitempty,并理解数组与切片差异。文中提供基础示例及时间类型处理的实战代码,帮助读者掌握JSON操作。
17 1
Golang深入浅出之-Go语言JSON处理:编码与解码实战
|
4天前
|
前端开发
【专栏】如何理解 package.json 中的 proxy 字段?
【4月更文挑战第29天】`package.json`的`proxy`字段用于配置开发环境中的代理服务器,解决跨域问题并模拟后端响应。它是字符串类型,值为代理服务器地址。主要应用场景包括前端跨域请求和本地调试。配置时在`package.json`顶层添加`proxy`字段,如`&quot;proxy&quot;: &quot;http://localhost:8080&quot;`。该配置仅在开发环境中生效,生产环境需另寻解决方案。
|
21天前
|
小程序 开发者
【微信小程序】微信开发者工具 app.json: [“subpackages“][0][“root“] 字段需为目录 已解决
【微信小程序】微信开发者工具 app.json: [“subpackages“][0][“root“] 字段需为目录 已解决
13 0
|
2月前
|
JSON 数据格式
糊涂工具类(hutool)post请求设置body参数为json数据
糊涂工具类(hutool)post请求设置body参数为json数据
100 1
|
3月前
|
SQL JSON 关系型数据库
sql如何获取字段里的json值
sql如何获取字段里的json值
|
4月前
|
JSON 关系型数据库 MySQL
这个问题是由于Flink的Table API在处理MySQL数据时,将MULTISET类型的字段转换为了JSON格式
【1月更文挑战第17天】【1月更文挑战第84篇】这个问题是由于Flink的Table API在处理MySQL数据时,将MULTISET类型的字段转换为了JSON格式
34 1