golangjsoniterextension处理动态字段的实现方法
短信预约 -IT技能 免费直播动态提醒
1. 背景
golang 原生 json 包,在处理 json 对象的字段的时候,是需要严格匹配类型的。但是,实际上,当我们与一些老系统或者脚本语言的系统对接的时候,有时候需要对类型需要做一下兼容,假设我们有以下需求
目标类型 | 输入 | 解析后 | |
---|---|---|---|
int | int, string | 123, “123” | 123 |
string | int, string | 123, “123” | “123” |
time | unix_seconds, RFC3339 | 1680676884, “2023-04-05T14:41:24Z”, | “2023-04-05T14:41:24Z” |
2. 可选项
我们以 time 作为一个样例
- 包装类,然后重新实现 Unmarshal 接口
type MyTime struct {
t time.Time
}
功能可以实现,但是如果使用的地方很多的情况下,就可能要改动多处
,而且,这是全局级别
的,可能会影响到很多包的行为
- 使用 jsonter 的 extension 实现
jsoniter 的插件文档参考
我们使用实例级别的 extension, 而非全局,可以针对不同业务逻辑有所区分
package main
import (
"fmt"
"reflect"
"strconv"
"time"
"unsafe"
jsoniter "github.com/json-iterator/go"
"github.com/modern-go/reflect2"
)
type sampleExtension struct {
jsoniter.DummyExtension
}
type wrapEncoder struct {
encodeFunc func(ptr unsafe.Pointer, stream *jsoniter.Stream)
isEmptyFunc func(ptr unsafe.Pointer) bool
decodeFunc func(ptr unsafe.Pointer, iter *jsoniter.Iterator)
}
func (enc *wrapEncoder) Encode(ptr unsafe.Pointer, stream *jsoniter.Stream) {
enc.encodeFunc(ptr, stream)
}
func (codec *wrapEncoder) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) {
codec.decodeFunc(ptr, iter)
}
func (enc *wrapEncoder) IsEmpty(ptr unsafe.Pointer) bool {
if enc.isEmptyFunc == nil {
return false
}
return enc.isEmptyFunc(ptr)
}
// 这里统一改用 unix seconds 进行输出
func (e *sampleExtension) CreateEncoder(typ reflect2.Type) jsoniter.ValEncoder {
if typ.Kind() == reflect.Struct && typ.Type1().PkgPath() == "time" && typ.String() == "time.Time" {
return &wrapEncoder{
func(ptr unsafe.Pointer, stream *jsoniter.Stream) {
t := *(*time.Time)(ptr)
data := strconv.Itoa(int(t.Unix()))
stream.WriteRaw(data)
},
nil,
nil,
}
}
return nil
}
func (e *sampleExtension) CreateDecoder(typ reflect2.Type) jsoniter.ValDecoder {
if typ.Kind() == reflect.Struct && typ.Type1().PkgPath() == "time" && typ.String() == "time.Time" {
return &wrapEncoder{
decodeFunc: func(ptr unsafe.Pointer, iter *jsoniter.Iterator) {
switch iter.WhatIsNext() {
case jsoniter.NumberValue: // 兼容 unix 数字解析
timeUnix := iter.ReadInt()
newTime := time.Unix(int64(timeUnix), 0)
*(*time.Time)(ptr) = newTime
case jsoniter.NilValue:
iter.Skip()
case jsoniter.StringValue:
timeStr := iter.ReadString()
newTime, err := time.Parse(time.RFC3339, timeStr)
if err != nil {
fmt.Println("Unmarshal err", err)
}
*(*time.Time)(ptr) = newTime
}
},
}
}
return nil
}
type Person struct {
Birth time.Time `json:"birth"`
}
func main() {
extension := &sampleExtension{}
jsoniterAPI := jsoniter.Config{}.Froze()
jsoniterAPI.RegisterExtension(extension)
var p1 = Person{
Birth: time.Now(),
}
j, err := jsoniterAPI.MarshalToString(p1)
if err != nil {
panic(err)
}
fmt.Println(j)
var p2 Person
err = jsoniterAPI.Unmarshal([]byte(`{"birth": 1680254527}`), &p2)
if err != nil {
panic(err)
}
fmt.Println("p2", p2)
var p3 Person
err = jsoniterAPI.Unmarshal([]byte(`{"birth": "2023-03-21T07:20:04+00:00"}`), &p3)
if err != nil {
panic(err)
}
fmt.Println("p3", p3)
var p4 Person
err = jsoniterAPI.Unmarshal([]byte(`{"birth": null}`), &p4)
if err != nil {
panic(err)
}
fmt.Println("p4", p4)
}
我们在例子中,实现了:
- 把 p1 使用了 unix 数字进行序列化
- 在反序列化 p2/p3/p4的时候,兼容了
字符串/数字/null
总结
jsoniter 包提供了比较完善的定制能力,通过例子可以感受一下扩展性。后续大家可以根据业务需求发掘更多的能力
到此这篇关于golang jsoniter extension 处理动态字段的文章就介绍到这了,更多相关go动态字段内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341