Golang JSON Unmarshaler 接口使用场景
今天在解析 Facebook Graph API 返回结果时,Golang 的 encoding/json
包中的 Unmarshaler 接口 发挥了非常不错的效果。
远端 API 返回的 JSON 结构大概是这样:
{
"name": "Hello World",
"first_name": "World",
"last_name": "Hello",
"picture": {
"data": {
"url": "https://lkebin.com/helloworld.png"
}
}
}
针对这个数据,在 Golang 代码中定义的结构这样:
type Profile struct {
Name string `json:"name"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Picture string `json:"picture"`
}
在程序代码中我希望 Profile.Picture
只是一个 URL 字符串,方便使用。而远端返回的数据却是一个多层级的结构,就不能直接把远端返回的数据按照预期的效果映射到Profile
结构体中。
以往使用其它语言处理这种情况时,可能会使用一些中间处理代码,先把返回的数据解析出来,再提取数据创建目标结构。或者直接把原始数据处理成目标结构后再解析。虽然都能实现,但不是那么优雅。
这个时候,encoding/json
包中提供的 Unmarshaler
接口就派上用场了。
目标是在解析远端 API 返回的这一段数据时:
{
"picture": {
"data": {
"url": "https://lkebin.com/helloworld.png"
}
}
}
能做到像解析下面这段一样:
{
"picture": "https://lkebin.com/helloworld.png"
}
Unmarshaler
接口提供了各类型自定义 JSON 解析的能力,那 Profile
结构中的 Picture
字段类型如果也可以自定义 JSON 解析的方式,就能实现目标效果了。先把 Profile.Picture
换成自定义类型:
type Picture string
type Profile struct {
...
Picture Picture `json:"picture"`
}
字段数据类型换成了自定义的 Picture
类型,下面就来为 Picture
类型实现自定义的 JSON 解析方式。Unmarshaler
接口定义如下,只有一个方法。
type Unmarshaler interface {
UnmarshalJSON([]byte) error
}
实现
func (p *Picture) UnmarshalJSON(b []byte) error {
// 定义一个能够映射原始数据中 picture 的结构
var pictureData struct {
Data struct {
Url string `json:"url"`
} `json:"data"`
}
// 把原始数据中的 picture 映射到定义的结构中
if err := json.Unmarshal(b, &pictureData); err != nil {
return err
}
// 提取目标数据,替换原始数据
*p = Picture(pictureData.Data.Url)
return nil
}
这样一来,当我在接收到远端 API 返回时,就可以直接把返回的数据解析到目标结构中。虽然自定义的 UnmarshalJSON
实现方法同样使用了中间代码来处理,但它却让处理过程变成非常简单。
func main() {
var j = `{
"name": "Hello World",
"first_name": "World",
"last_name": "Hello",
"picture": {
"data": {
"url": "https://lkebin.com/helloworld.png"
}
}
}`
var profile Profile
json.Unmarshal([]byte(j), &profile)
}
Gist 有完整的代码