golang Json 自定义类型 MarshalJSON/UnMarshalJSON 接口应用

需求1

解决浮点数据类型编码小数位丢失的问题

golang 的原生 json package 有时会有一些与预期不符合的情况, 例如对接编码(json.Marshal) golang 会默认 “整型浮点数” 如: 1.00 转换为json 的整型 1, 但有时并不希望这种转换.

例1:

package main

import (
    "encoding/json"
    "fmt"
    "strconv"
)

type Person struct {
    Name string
    Util float64
}

type StrictFloat64 float64

// 自定义类型上实现 Marshaler 的接口, 在进行 Marshal 时就会使用此除的实现来进行 json 编码
func (f StrictFloat64) MarshalJSON() ([]byte, error) {
    if float64(f) == float64(int(f)) {
        return []byte(strconv.FormatFloat(float64(f), 'f', 2, 64)), nil // 可以自由调整精度
    }
    return []byte(strconv.FormatFloat(float64(f), 'f', -1, 64)), nil
}

type StrictPerson struct {
    Name string
    Util StrictFloat64
}

func main() {
    p1 := Person{"ross", 1.01}
    p2 := Person{"jack", 1.00} // json 中 1.00 将会变为 1
    p1Json, _ := json.Marshal(p1)
    fmt.Println("\"ross\", 1.01 --> " + string(p1Json))
    p2Json, _ := json.Marshal(p2)
    fmt.Println("\"ross\", 1.00 --> " + string(p2Json))

    sp1 := StrictPerson{"ross", 1.01}
    sp2 := StrictPerson{"jack", 1.00} // json 中 1.00 将会小数位
    sp1Json, _ := json.Marshal(sp1)
    fmt.Println("\"ross\", 1.01 (自定义 StrictFloat64 类型)--> " + string(sp1Json))
    sp2Json, _ := json.Marshal(sp2)
    fmt.Println("\"ross\", 1.00 (自定义 StrictFloat64 类型) --> " + string(sp2Json))
}


// 输出:
// "ross", 1.01 --> {"Name":"ross","Util":1.01}
// "ross", 1.00 --> {"Name":"jack","Util":1}
// "ross", 1.01 (自定义 StrictFloat64 类型)--> {"Name":"ross","Util":1.01}
// "ross", 1.00 (自定义 StrictFloat64 类型) --> {"Name":"jack","Util":1.00}

需求二

解决非2 RFC3339 标准格式时间转换问题

golang 使用了 RFC3339 作为其 JSON 转换时候的标准格式, 但是很多时候我们接受到的数据, 或者我们要发送的数据, 往往有着不同的事件格式, 有些时候执行将这些非 RFC3339 格式当作字符串来解析.
如果想要实现再 JSON 编码解码时无缝的兼容各种时间格式, 就需要我们自定义一个时间类型去完成相应的JSON 转换工作了

最核心的地方就是我们需要自定义一个数据类型来进行 JSON 处理, Golang 的标准 Json 在处理各种数据类型是都是调用其类型接口 UnmarshalJSON MarshalJSON 进行转换的, 所以我们只要实现了这两个接口, 就可以实现自定义类型的 JSON 编码解码
本例中并没有直接给 time.Time 类型定义一个别名, 然后再在这个别名上定义 UnmarshalJSON / MarshalJSON, 而是使用了 Golang 匿名结构体的特性实现了 ExTime 对 time.Time 的 “伪继承” 这样 “伪继承” 出来的结构体是可以调用 time.Time 的所有方法的

type ExTime struct {
	time.Time
}

func (t *ExTime) UnmarshalJSON(b []byte) error {
	b = bytes.Trim(b, "\"")  \\ 此除需要去掉传入的数据的两端的 "" 
	ext, err := time.Parse(t.UnmarshalTimeFormat(), string(b))
	if err != nil {
		// do something
	}
	*t = ExTime{ext}
	return nil
}

func (t ExTime) MarshalJSON() ([]byte, error) {
	var stamp = fmt.Sprintf("\"%s\"", time.Time(t.Time).Format(t.MarshalTimeFormat()))
	return []byte(stamp), nil
}

那么如何实现完全自定义的时间格式呢, 最简单的办法就是再进行 sting -> time 和 time-> sting 时指定特定的格式.
可以看到上面的代码中使用了两个函数 UnmarshalTimeFormat / MarshalTimeFormat 这两个函数就是用来控制格式的
我在 ExTime 定义了两个获取格式的函数, 分别用于 UnmarshalJSON / MarshalJSON 时的格式控制, 其实就是简单的返回一个全局变量, 这里我并没有吧这两个全局变量定义为 const 格式, 主要就是为了可以在程序中灵活的改变这两个值

var ExTimeUnmarshalTimeFormat = "2006-01-02 15:04:05"
var ExTimeMarshalTimeFormat = "2006-01-02 15:04:05"

func (t ExTime) UnmarshalTimeFormat() string {
	return ExTimeUnmarshalTimeFormat
}

func (t ExTime) MarshalTimeFormat() string {
	return ExTimeMarshalTimeFormat
}

完整代码如下, 可以看到再程序中, 通过改变 ExTimeUnmarshalTimeFormat 和 ExTimeMarshalTimeFormat 两个值, 就可以实现任意时间格式的 JSON 编解码了.(对于 ExTimeUnmarshalTimeFormat 和 ExTimeMarshalTimeFormat 的赋值操作, 实际上更好做法的应该是定义 Set 函数接口, 并在其中可以检查格式的有效性等等 )

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"time"
)

var ExTimeUnmarshalTimeFormat = "2006-01-02 15:04:05"
var ExTimeMarshalTimeFormat = "2006-01-02 15:04:05"

type ExTime struct {
	time.Time
}

func (t ExTime) UnmarshalTimeFormat() string {
	return ExTimeUnmarshalTimeFormat
}

func (t ExTime) MarshalTimeFormat() string {
	return ExTimeMarshalTimeFormat
}

type Event struct {
	EventId int64  `json:"id"`
	Stamp   ExTime `json:"stamp"`
}

func (t *ExTime) UnmarshalJSON(b []byte) error {
	b = bytes.Trim(b, "\"")
	ext, err := time.Parse(t.UnmarshalTimeFormat(), string(b))
	if err != nil {
		// do something
	}
	*t = ExTime{ext}
	return nil
}

func (t ExTime) MarshalJSON() ([]byte, error) {
	var stamp = fmt.Sprintf("\"%s\"", time.Time(t.Time).Format(t.MarshalTimeFormat()))
	return []byte(stamp), nil
}

func main() {
	e1 := `{"id":5,"stamp":"2006-01-02 15:04:05"}`
	p := new(Event)
	_ = json.Unmarshal([]byte(e1), p)
	fmt.Println(p.Stamp) // output : 2006-01-02 15:04:05 +0000 UTC

	e2 := `{"id":5,"stamp":"Mon Jan 2 15:04:05 2006"}`
	ExTimeUnmarshalTimeFormat = "Mon Jan _2 15:04:05 2006"
	p2 := new(Event)
	_ = json.Unmarshal([]byte(e2), p2)
	fmt.Println(p2.Stamp) // output : 2006-01-02 15:04:05 +0000 UTC

	ej1, _ := json.Marshal(p)
	fmt.Println(string(ej1)) // output : {"id":5,"stamp":"2006-01-02 15:04:05"}

	ExTimeMarshalTimeFormat = "Mon Jan _2 15:04:05 2006"
	ej2, _ := json.Marshal(p)
	fmt.Println(string(ej2)) // output : {"id":5,"stamp":"Mon Jan  2 15:04:05 2006"}
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值