golang jsoniter extension 處理動態(tài)字段的實(shí)現(xiàn)方法
1. 背景
golang 原生 json 包,在處理 json 對象的字段的時候,是需要嚴(yán)格匹配類型的。但是,實(shí)際上,當(dāng)我們與一些老系統(tǒng)或者腳本語言的系統(tǒng)對接的時候,有時候需要對類型需要做一下兼容,假設(shè)我們有以下需求
| 目標(biāo)類型 | 輸入 | 解析后 | |
|---|---|---|---|
| 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 作為一個樣例
- 包裝類,然后重新實(shí)現(xiàn) Unmarshal 接口
type MyTime struct {
t time.Time
}功能可以實(shí)現(xiàn),但是如果使用的地方很多的情況下,就可能要改動多處,而且,這是全局級別的,可能會影響到很多包的行為
- 使用 jsonter 的 extension 實(shí)現(xiàn)
jsoniter 的插件文檔參考
我們使用實(shí)例級別的 extension, 而非全局,可以針對不同業(yè)務(wù)邏輯有所區(qū)分
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)
}
// 這里統(tǒng)一改用 unix seconds 進(jìn)行輸出
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 數(shù)字解析
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)
}
我們在例子中,實(shí)現(xiàn)了:
- 把 p1 使用了 unix 數(shù)字進(jìn)行序列化
- 在反序列化 p2/p3/p4的時候,兼容了
字符串/數(shù)字/null
總結(jié)
jsoniter 包提供了比較完善的定制能力,通過例子可以感受一下擴(kuò)展性。后續(xù)大家可以根據(jù)業(yè)務(wù)需求發(fā)掘更多的能力
到此這篇關(guān)于golang jsoniter extension 處理動態(tài)字段的文章就介紹到這了,更多相關(guān)go動態(tài)字段內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Golang實(shí)現(xiàn)自己的Redis(pipeline客戶端)實(shí)例探索
這篇文章主要為大家介紹了Golang實(shí)現(xiàn)自己的Redis(pipeline客戶端)實(shí)例探索,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01
使用Gin框架搭建一個Go Web應(yīng)用程序的方法詳解
在本文中,我們將要實(shí)現(xiàn)一個簡單的 Web 應(yīng)用程序,通過 Gin 框架來搭建,主要支持用戶注冊和登錄,用戶可以通過注冊賬戶的方式創(chuàng)建自己的賬號,并通過登錄功能進(jìn)行身份驗(yàn)證,感興趣的同學(xué)跟著小編一起來看看吧2023-08-08
Go語言使用templ實(shí)現(xiàn)編寫HTML用戶界面
templ是一個在 Go 中編寫 HTML 用戶界面的語言,使用 templ,我們可以創(chuàng)建可呈現(xiàn) HTML 片段的組件,下面就跟隨小編一起了解一下具體的實(shí)現(xiàn)方法吧2023-12-12

