高级JSON定制化
通过使用结构体标签、添加空白和封装响应数据,我们已经能够为JSON响应添加大量定制信息。但是,当这些内容还不够时,您需要更自由地定制JSON时,会发生什么呢?
要回答这个问题,我们首先需要谈谈Go如何处理JSON序列化的一些理论。要理解的关键是:
Go是在什么时候将特殊类型序列化为JSON,它首先查看对应的类型是否实现了MarshalJSON()方法。如果实现了,GO将调用这个方法来决定JSON编码格式。
这么讲有点模糊,我们更精确点。严格地说,当Go将特定类型编码为JSON时,它会查看该类型是否满足json.Marshaler接口,该接口如下所示:
typeMarshalerinterface{MarshalJSON()([]byte,error)}
如果类型确实满足接口,那么Go将调用它的MarshalJSON()方法,并使用它返回的[]byte切片作为JSON编码的值。
如果该类型没有MarshalJSON()方法,那么Go将返回尝试根据自己的内部规则将其编码为JSON。
因此,如果我们想定制某些类型的编码方式,只需要在其上实现MarshalJSON()方法,该方法以[]byte类型返回自定义的JSON内容。
提示:如果您查看time.Time类型源代码,就可以看到这一点。time.Time实际上是一个结构体,但是它有一个MarshalJSON()方法,输出RFC格式JSON对象。当time.Time值被序列化为JSON对象时,就会调用MarshalJSON()方法。
定制电影Runtime字段JSON序列化
为了说明这一点,让我们看一下应用程序中的一个具体事例。
当我们的Movie结构被编码为JSON时,Runtime字段(它是一个int32类型)编码为JSON数字。现在我们来更改它,将其编码为"runtimemins“的字符串。像这样:
{"id":,"title":"Casablanca","runtime":"mins","genres":["drama","romance","war"],"version":1}
有几种方法可以实现这一点,但一种简单的方法是为Runtime字段创建一个自定义类型,并在这个类型上实现MarshalJSON()方法。
为了防止internal/data/movie.go文件不会太乱,我们创建一个新的文件来处理runtime类型序列化逻辑:
$touchinternal/data/runtime.go
然后继续添加以下代码:
packagedataimport("fmt""strconv")//申明Runtime类型,其底层是int32类型(和movie中的字段一样)typeRuntimeint32//实现MarshalJSON()方法,这样就实现了json.Marshaler接口。func(rRuntime)MarshalJSON()([]byte,error){//生成一个字符串包含电影时长jsonValue:=fmt.Sprintf("%dmins",r)//使用strconv.Quote()函数封装双引号。为了在JSON中以字符串对象输出,需要用双引号。quotedJSONValue:=strconv.Quote(jsonValue)//将字符串转为[]byte返回return[]byte(quotedJSONValue)}
这里我想强调两点:
如果您的MarshalJSON()方法向我们的方法一样返回一个JSON字符串值,那么您必须在返回字符串之前用双引号包装它。否则它将不会被解释为JSON字符串,你将收到类似于这样的运行时错误:
json:errorcallingMarshalJSONfortypedata.Runtime:invalidcharactermaftertop-levelvalue
我们故意为MarshalJSON()方法使用值接收器,而不是指针接收器func(r*Runtime)MarshalJSON()。这给了我们更多的灵活性,因为这意味着定制JSON编码将对Runtime值对象和指针对象都有效。
好的,现在有了自定义Runtime类型,打开internal/data/movies.go文件并更新Movie结构:
File:internal/data/movies.go
packagedataimport("time")typeMoviestruct{IDint64`json:"id"`CreateAttime.Time`json:"-"`Titlestring`json:"title"`Yearint32`json:"year,omitempty"`//使用Runtime类型取代int32,注意omitempty还是能生效的RuntimeRuntime`json:"runtime,omitempty,string"`Genres[]string`json:"genres,omitempty"`Versionint32`json:"version"`}
重启服务然后对GET/v1/movies/:id接口发起请求。你应该看到一个包含自定义runtime值的响应,格式为"xxmins",类似如下:
$curllocalhost:/v1/movies/{"movie":{:,:,:,:[,,],:1}}
总之,这是定制JSON序列化的一种很好的方法。我们的代码简洁明了,并且我们有一个自定义的Runtime类型,可以随时随地使用它。
但也有不利的一面。在将代码与其他包集成时,使用自定义类型有时会很尴尬,您可能需要执行类型转换,将自定义类型转换为其他包理解和可接受的值。
转载请注明:http://www.guyukameng.com/aspnet/aspnet/2023-04-21/16691.html