【Go Web开发】定制JSON序列化

高级JSON定制化

通过使用结构体标签、添加空白和封装响应数据,我们已经能够为JSON响应添加大量定制信息。但是,当这些内容还不够时,您需要更自由地定制JSON时,会发生什么呢?

要回答这个问题,我们首先需要谈谈Go如何处理JSON序列化的一些理论。要理解的关键是:

Go是在什么时候将特殊类型序列化为JSON,它首先查看对应的类型是否实现了MarshalJSON()方法。如果实现了,GO将调用这个方法来决定JSON编码格式。

这么讲有点模糊,我们更精确点。严格地说,当Go将特定类型编码为JSON时,它会查看该类型是否满足json.Marshaler接口,该接口如下所示:

type Marshaler interface {
    MarshalJSON() ([]byte, error)
}

如果类型确实满足接口,那么Go将调用它的MarshalJSON()方法,并使用它返回的[]byte切片作为JSON编码的值。

如果该类型没有MarshalJSON()方法,那么Go将返回尝试根据自己的内部规则将其编码为JSON。

因此,如果我们想定制某些类型的编码方式,只需要在其上实现MarshalJSON()方法,该方法以[]byte类型返回自定义的JSON内容。

提示:如果您查看time.Time类型源代码,就可以看到这一点。time.Time实际上是一个结构体,但是它有一个MarshalJSON()方法,输出RFC3339格式JSON对象。当time.Time值被序列化为JSON对象时,就会调用MarshalJSON()方法。

定制电影Runtime字段JSON序列化

为了说明这一点,让我们看一下应用程序中的一个具体示例。

当我们的Movie结构被编码为JSON时,Runtime字段(它是一个int32类型)编码为JSON数字。现在我们来更改它,将其编码为"<runtime> mins“的字符串。像这样:

{
    "id": 123,
    "title": "Casablanca",
    "runtime": "102 mins",
    "genres":
    [
        "drama",
        "romance",
        "war"
    ],
    "version": 1
}

有几种方法可以实现这一点,但一种简单的方法是为Runtime字段创建一个自定义类型,并在这个类型上实现MarshalJSON()方法。

为了防止internal/data/movie.go文件不会太乱,我们创建一个新的文件来处理runtime类型序列化逻辑:

 $ touch internal/data/runtime.go

然后继续添加以下代码:

package data

import (
    "fmt"
    "strconv"
)

//申明Runtime类型,其底层是int32类型(和movie中的字段一样)
type Runtime int32

//实现MarshalJSON()方法,这样就实现了json.Marshaler接口。
func (r Runtime) MarshalJSON() ([]byte, error) {
    //生成一个字符串包含电影时长
    jsonValue := fmt.Sprintf("%d mins", r)

    //使用strconv.Quote()函数封装双引号。为了在JSON中以字符串对象输出,需要用双引号。
    quotedJSONValue := strconv.Quote(jsonValue)
    //将字符串转为[]byte返回
    return []byte(quotedJSONValue)
}

这里我想强调两点:

  • 如果您的MarshalJSON()方法像我们的方法一样返回一个JSON字符串值,那么您必须在返回字符串之前用双引号包装它。否则它将不会被解释为JSON字符串,你将收到类似于这样的运行时错误:
    json: error calling MarshalJSON for type data.Runtime: invalid character 'm' after top-level value
    
  • 我们故意为MarshalJSON()方法使用值接收器,而不是指针接收器func (r *Runtime) MarshalJSON()。这给了我们更多的灵活性,因为这意味着定制JSON编码将对Runtime值对象和指针对象都有效。正如Effective Go提到的:

如果你不确定指针和值接收器之间的区别,那么这篇博客提供了一个很好的总结。

好的,现在有了自定义Runtime类型,打开internal/data/movies.go文件并更新Movie结构:

File: internal/data/movies.go


package data

import (
    "time"
)

type Movie struct {
    ID       int64     `json:"id"`
    CreateAt time.Time `json:"-"`
    Title    string    `json:"title"`
    Year     int32     `json:"year,omitempty"`
        //使用Runtime类型取代int32,注意omitempty还是能生效的
    Runtime  Runtime   `json:"runtime,omitempty,string"`
    Genres   []string  `json:"genres,omitempty"`
    Version  int32     `json:"version"`
}

重启服务然后对GET /v1/movies/:id接口发起请求。你应该看到一个包含自定义runtime值的响应,格式为"xx mins",类似如下:

$ curl localhost:4000/v1/movies/123
{
    "movie":
    {
        "id": 123,
        "title": "Casablanca",
        "runtime": "102 mins",
        "genres":
        [
            "drama",
            "romance",
            "war"
        ],
        "version": 1
    }
}

总之,这是定制JSON序列化的一种很好的方法。我们的代码简洁明了,并且我们有一个自定义的Runtime类型,可以随时随地使用它。

但也有不利的一面。在将代码与其他包集成时,使用自定义类型有时会很尴尬,您可能需要执行类型转换,将自定义类型转换为其他包理解和可接受的值。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容