3

golang jsoniter extension 处理动态字段

 1 year ago
source link: https://blog.csdn.net/oqqYuan1234567890/article/details/129970519
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

golang jsoniter extension 处理动态字段

original.png
皿小草 newUpTime2.png 已于 2023-04-05 15:47:43 修改 articleReadEyes2.png 14

golang 原生 json 包,在处理 json 对象的字段的时候,是需要严格匹配类型的。但是,实际上,当我们与一些老系统或者脚本语言的系统对接的时候,有时候需要对类型需要做一下兼容,假设我们有以下需求

目标类型输入解析后
intint, string123, “123”123
stringint, string123, “123”“123”
timeunix_seconds, RFC33391680676884, “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)
    }
    
    newCodeMoreWhite.png

我们在例子中,实现了:

  • 把 p1 使用了 unix 数字进行序列化
  • 在反序列化 p2/p3/p4的时候,兼容了 字符串/数字/null

jsoniter 包提供了比较完善的定制能力,通过例子可以感受一下扩展性。后续大家可以根据业务需求发掘更多的能力

文章知识点与官方知识档案匹配,可进一步学习相关知识
Go技能树首页概览3007 人正在系统学习中

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK