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
}

附录


相关文章
|
14天前
|
JSON Java 数据格式
springboot中表字段映射中设置JSON格式字段映射
springboot中表字段映射中设置JSON格式字段映射
34 1
|
11天前
|
安全 JavaScript
如何在`package.json`中正确设置依赖版本范围?
正确设置 `package.json` 中的依赖版本范围需要综合考虑项目的需求、依赖库的稳定性和兼容性,以及开发和维护的便利性等因素。通过合理选择版本范围符号,并结合定期的审查和测试,可以有效地管理项目依赖,确保项目的稳定运行。
20 1
|
1月前
|
JSON API 数据格式
postman如何发送json请求其中file字段是一个图片
postman如何发送json请求其中file字段是一个图片
120 4
|
6月前
|
JSON NoSQL MongoDB
实时计算 Flink版产品使用合集之要将收集到的 MongoDB 数据映射成 JSON 对象而非按字段分割,该怎么操作
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
|
6月前
|
存储 JSON 数据处理
从JSON数据到Pandas DataFrame:如何解析出所需字段
从JSON数据到Pandas DataFrame:如何解析出所需字段
398 1
|
2月前
|
JSON 数据库 数据格式
数据库表如果有json字段,该怎么更新
数据库表如果有json字段,该怎么更新
|
3月前
|
JSON 人工智能 编译器
Go json 能否解码到一个 interface 类型的值
Go json 能否解码到一个 interface 类型的值
30 1
|
3月前
|
JSON Go 数据格式
Go - json.Unmarshal 遇到的小坑
Go - json.Unmarshal 遇到的小坑
59 9
|
3月前
|
JSON Go 数据格式
Go实现json字符串与各类struct相互转换
文章通过Go语言示例代码详细演示了如何实现JSON字符串与各类struct之间的相互转换,包括结构体对象生成JSON字符串和JSON字符串映射到struct对象的过程。
31 0
|
3月前
|
JSON JavaScript 前端开发
Vue项目使用Cookie,以Json格式存入与读取Cookie,设置过期时间以及删除操作
这篇文章介绍了在Vue项目中如何使用JavaScript操作Cookie,包括设置、读取、设置过期时间以及删除Cookie的方法。
271 0