在golang中使用json区分空字段和未设置字段

A few weeks ago, I was working on a Golang microservice where I needed to add support for CRUD operations with JSON data. Normally, I would make a structure for the entity with all the fields defined in it along with ‘omitempty’ attribute as shown

几周前,我正在研究Golang微服务,需要在其中添加对使用JSON数据进行CRUD操作的支持。 通常,我会为实体创建一个结构,其中定义了所有字段以及'omitempty'属性,如下所示

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

Problem

问题

But this kind of representation poses a serious problem, especially for Update or Edit operations.

但是这种表示形式带来了严重的问题,尤其是对于Update或Edit操作。

For example, let’s say that the update request JSON looks something like this

例如,假设更新请求JSON看起来像这样

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

Notice the empty desc field. Now let's see how it is unmarshalled in Go

注意空的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:}

Here description comes as an empty string, It’s clearly visible that the client wants to set desc as an empty string and that is inferred by our program.

这里的描述是一个空字符串,很明显,客户端希望将desc设置为一个空字符串,这是由我们的程序推断出来的。

But what if the client does not want the change the existing value for Description, in that case, sending a big description string again is not the right thing to do, hence the JSON would look like this

但是,如果客户端不希望更改Description的现有值,在那种情况下,再次发送大的描述字符串是不正确的事情,因此JSON看起来像这样

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

Let’s unmarshal it into our structure

让我们将其解组到我们的结构中

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

Well, we still get Desc as an empty string , so how do we differentiate between not-set field and empty field

好了,我们仍然将Desc作为空字符串获取,那么如何区分未设置字段和空字段

Short answer? Pointers

简短的答案? 指针

Solution

This is inspired by some existing Golang libraries like go-github. We can change our struct fields to pointer types, which would look like this

这受一些现有的Golang库(例如go-github )的启发。 我们可以将结构字段更改为指针类型,如下所示

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

By doing this we add an extra state to our fields. If the field does not exist in the raw JSON then the struct field will be null (nil).

通过这样做,我们向字段添加了额外的状态。 如果原始JSON中不存在该字段,则struct字段将为null( nil )。

On the other hand, if the field does exist and its value is empty, then the pointer is not null and the field contains the empty value.

另一方面,如果该字段确实存在并且其值为空,则指针不为null,并且该字段包含空值。

Note- I did not change ‘Id’ to a pointer type because it cannot have a null state, id needs to be present at all times, its similar to a database id.

注意 -我没有将' Id'更改为指针类型,因为它不能具有null状态,id必须始终存在,类似于数据库id。

Let’s try it out.

让我们尝试一下。

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)

In the first case, as the description is set to an empty string, we get a non-null pointer in Desc with an empty string value. In the second case , where the field is not-set we get a null string pointer.

在第一种情况下,由于描述设置为空字符串,因此在Desc中获得了一个非空指针,该指针具有空字符串值。 在第二种情况下,未设置字段,我们得到一个空字符串指针。

Hence we are able to differentiate between the two kinds of updates. This way works not just for strings but all the other data types including integers, nested structs, etc.

因此,我们能够区分两种更新。 这种方式不仅适用于字符串,而且适用于所有其他数据类型,包括整数,嵌套结构等。

But this approach also comes with some problems.

但是这种方法也存在一些问题。

Null Safety: Non-pointer data types have inherent null safety. Meaning, a string or int can never be null in Golang. They always have a default value. But if pointers are defined then those data types are null by default if not set manually. Hence trying to access the data of those pointers without verifying the nullability can lead to crashes in your application.

空安全性:非指针数据类型具有固有的空安全性。 这意味着在Golang中,字符串或int永远不能为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)
}

This can be easily fixed by always checking for null pointers, but may make your code look dirty.

通过始终检查空指针可以很容易地解决此问题,但可能会使您的代码看起来很脏。

Printability: As you might have noticed in the pointer-based solution output, the value of the pointers is not printed. Instead, the hex pointer is printed which is not very useful in applications. This can also be overcome by implementing the stringer interface.

可打印性:正如您在基于指针的解决方案输出中可能已经注意到的那样,不会打印指针的值。 而是打印十六进制指针,这在应用程序中不是很有用。 这也可以通过实现纵梁接口来克服。

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
}

Appendix:

附录:

翻译自: https://medium.com/@arpitkh96/differentiate-between-empty-and-not-set-fields-with-json-in-golang-957bb2c5c065

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值