golang自定义MarshalJSON、UnmarshalJSON 原理和技巧

问题出现的原因:在前后端分离的项目中,经常出现的问题是时间戳格式的问题。

后端的日期格式兼容性强,比较完善。前端由于各种原因,日期格式不完善。

就会产生矛盾。

ms int64比较通用,但是unix时间没有可读性,不方便db运维的工作。

以golang为例,讲一下时间戳转换,在内存里面转换时间戳。

golang的时间戳类型叫做:time.Time,标准是RFC 3339。

RFC 3339 是一种日期 - 时间格式的互联网标准,它基于 ISO 8601 格式。其基本格式为YYYY - MM - DDTHH:MM:SS±HH:MM,其中:

  • YYYY表示四位年份,例如2024。
  • MM表示两位月份,范围是01 - 12。
  • DD表示两位日期,范围是01 - 31。
  • T是日期和时间部分的分隔符,它是一个固定的字符,用于区分日期和时间。
  • HH表示两位小时数,采用 24 小时制,范围是00 - 23。
  • MM表示两位分钟数,范围是00 - 59。
  • SS表示两位秒数,范围是00 - 59。
  • ±HH:MM表示时区偏移量,其中+或-表示相对于 UTC(协调世界时)的偏移方向,HH和MM分别表示小时和分钟的偏移量。例如,+08:00表示东八区,比 UTC 快 8 小时;-05:00表示西五区,比 UTC 慢 5 小时。

例如:

  • 2024-06-15T14:30:00+00:00:表示 2024 年 6 月 15 日,下午 2 点 30 分(14:30),时区为 UTC(偏移量为+00:00)。
  • 2024-12-31T23:59:59-05:00:表示 2024 年 12 月 31 日,晚上 11 点 59 分 59 秒(23:59:59),时区为西五区(偏移量为-05:00)。

为方便展示,我绘制了下面的草图,一个https请求从客户端到达数据库,需要经历的最短路径。 nginx作为入口时,但是数据没有格式化,不适合作为拦截点。 往右边继续看,很明显的发现数据流只有在json 序列化/反序列化的时候开始汇聚。 那RFC 3339 标准的time.Time转换为毫秒在这里实现,无疑是工作量最小的修改方式。

(就像小时候,灌酿造的酱油时,在瓶口处放置一个滤网,过滤掉杂质。 也好像查干湖捕鱼时,工人站在冰面出口处将一个一个的鱼勾起来。 又好像,蜀黍办案,在高速路口排兵布阵,是一样的道理。 在数据处理,找到数据的入口 或 出口,然后轻松拿捏。)

(golang语法或代码没有什么好讲的,总可以借鉴或自定义,属于闻道有先后性质的,没有高低之分。 但是一种思维方式,值得推而广之,用在工作和生活的方方面面,减少你前行的阻力。)

自定义MarshalJSON, UnmarshalJSON。当应用调用json.Marshal(), json.UnMarshal()时就会调用自定义解析函数。

go 复制代码
type DevData struct {
        QrCodeStr   string  `json:"qrCodeStr"`
        StartTime      time.Time `json:"StartTime,omitempty" swaggerignore:"false"`
        StartTimeStamp int64     `json:"startTimeStamp"`
}


func (c *DevData) MarshalJSON() ([]byte, error) { 
        type Alias DevData

        aux := &Alias{}
        *aux = Alias(*c)
        aux.StartTime = time.Unix(aux.StartTimeStamp, 0)
        aux.EndTime = time.Unix(aux.EndTimeStamp, 0)

        if data, err := json.Marshal(aux); err == nil { 
                return data, nil                
        } else {
                return nil, err
        }
}

func (c *DevData) UnmarshalJSON(data []byte) error {
        type Alias DevData
        aux := &Alias{}
        if err := json.Unmarshal(data, aux); err != nil {
                return err
        }
        aux.StartTime = time.Unix(aux.StartTimeStamp, 0)
        aux.EndTime = time.Unix(aux.EndTimeStamp, 0)

        *c = DevData(*aux)

        return nil
}

在哪里产生关联:

当一个类型实现了encoding/json包中的json.Marshaler接口的MarshalJSON方法时,json.Marshal函数就会调用这个自定义的MarshalJSON方法来进行 JSON 序列化。json.Marshaler接口定义如下:

go 复制代码
type Marshaler interface {
    MarshalJSON() ([]byte, error)
}

type Unmarshaler interface {
    UnmarshalJSON([]byte) error
} 


其中的type Alias DevData,是为了避免套娃无线递归问题。

但是引入了新的问题: 对象多拷贝一次,当数据量比较大,api调用比较频繁的时候,浪费cpu时间。

可以用解析之后,用map遍历,我最开始写golang代码时就用过这个笨方法。

好的解决办法是:限定拷贝的数据范围,将需要特殊处理的时间戳,单独封装在一个结构体里面。这样只有特殊字段会多拷贝一次,其他字段不会出现拷贝。

go 复制代码
type TimeInfo struct {
        StartTime      time.Time `json:"StartTime,omitempty"`    // 设置标签:omitempty,客户端传入空值时,忽略该字段解析不会报错
        StartTimeStamp int64     `json:"startTimeStamp,omitempty" bson:"-,omitempty"` // 应用传入空值时,解析不报错。 bson注解表示,StartTimeStamp 是一个内存临时变量,不会写入mongodb数据库。
}

func (c *TimeInfo) MarshalJSON() ([]byte, error) { 
        type Alias TimeInfo

        aux := &Alias{}
        *aux = Alias(*c)
        aux.StartTime = time.Unix(aux.StartTimeStamp, 0)
        aux.EndTime = time.Unix(aux.EndTimeStamp, 0)

        if data, err := json.Marshal(aux); err == nil { 
                return data, nil                
        } else {
                return nil, err
        }
}

func (c *TimeInfo) UnmarshalJSON(data []byte) error {
        type Alias TimeInfo
        aux := &Alias{}
        if err := json.Unmarshal(data, aux); err != nil {
                return err
        }
        aux.StartTime = time.Unix(aux.StartTimeStamp, 0)
        aux.EndTime = time.Unix(aux.EndTimeStamp, 0)

        *c = TimeInfo(*aux)

        return nil
}

type DevData struct {
        QrCodeStr   string  `json:"qrCodeStr"`
		TimeInfo
}
相关推荐
Q_192849990616 分钟前
基于Spring Boot的找律师系统
java·spring boot·后端
ZVAyIVqt0UFji1 小时前
go-zero负载均衡实现原理
运维·开发语言·后端·golang·负载均衡
loop lee1 小时前
Nginx - 负载均衡及其配置(Balance)
java·开发语言·github
SomeB1oody2 小时前
【Rust自学】4.1. 所有权:栈内存 vs. 堆内存
开发语言·后端·rust
toto4122 小时前
线程安全与线程不安全
java·开发语言·安全
水木流年追梦2 小时前
【python因果库实战10】为何需要因果分析
开发语言·python
w(゚Д゚)w吓洗宝宝了3 小时前
C vs C++: 一场编程语言的演变与对比
c语言·开发语言·c++
AI人H哥会Java4 小时前
【Spring】Spring的模块架构与生态圈—Spring MVC与Spring WebFlux
java·开发语言·后端·spring·架构
开心工作室_kaic4 小时前
springboot461学生成绩分析和弱项辅助系统设计(论文+源码)_kaic
开发语言·数据库·vue.js·php·apache
毕设资源大全4 小时前
基于SpringBoot+html+vue实现的林业产品推荐系统【源码+文档+数据库文件+包部署成功+答疑解惑问到会为止】
java·数据库·vue.js·spring boot·后端·mysql·html