golang jsoniter extension 处理动态字段的实现方法

 更新时间:2023年04月06日 15:11:35   作者:皿小草  
这篇文章主要介绍了golang jsoniter extension 处理动态字段的实现方法,我们使用实例级别的 extension, 而非全局,可以针对不同业务逻辑有所区分,jsoniter 包提供了比较完善的定制能力,通过例子可以感受一下扩展性,需要的朋友可以参考下

1. 背景

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)
}

我们在例子中,实现了:

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

总结

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

到此这篇关于golang jsoniter extension 处理动态字段的文章就介绍到这了,更多相关go动态字段内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • golang进行xml文件解析的操作方法

    golang进行xml文件解析的操作方法

    本文介绍了Go语言中解析XML文件的几种方法:小文件解析、大文件流式解析和复杂结构解析,对于小文件,使用标准库中的encoding/xml包;对于大文件,采用流式解析以避免内存溢出,对于复杂结构的XML文件,推荐使用第三方库github.com/beevik/etree
    2024-11-11
  • 浅谈Golang内存逃逸

    浅谈Golang内存逃逸

    本文主要介绍了Golang内存逃逸,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-08-08
  • golang基于websocket通信tcp keepalive研究记录

    golang基于websocket通信tcp keepalive研究记录

    这篇文章主要为大家介绍了golang基于websocket通信tcp keepalive研究记录,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-06-06
  • Go语言如何并发超时处理详解

    Go语言如何并发超时处理详解

    大家都知道golang并没有在语言层次上提供超时操作,但可以通过一些小技巧实现超时。下面来一起看看吧,有需要的朋友们可以参考借鉴。
    2016-09-09
  • 详解golang函数多返回值错误处理与error类型

    详解golang函数多返回值错误处理与error类型

    这篇文章主要为大家详细介绍了golang中函数多返回值错误处理与error类型的相关知识,文中的示例代码简洁易懂,感兴趣的小伙伴快跟随小编一起学习吧
    2023-10-10
  • Go语言实现枚举的示例代码

    Go语言实现枚举的示例代码

    本文主要介绍了Go语言实现枚举的示例代码,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-01-01
  • golang 连接mongoDB的方法示例

    golang 连接mongoDB的方法示例

    这篇文章主要介绍了golang 连接mongoDB的方法示例,详细的介绍了golang的基础知识和连接mongoDB的方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-11-11
  • 详解go中panic源码解读

    详解go中panic源码解读

    这篇文章主要介绍了go中panic源码解读,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-04-04
  • go实现一个内存缓存系统的示例代码

    go实现一个内存缓存系统的示例代码

    本文主要介绍了go实现一个内存缓存系统的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-10-10
  • Go设计模式之观察者模式讲解和代码示例

    Go设计模式之观察者模式讲解和代码示例

    观察者是一种行为设计模式, 允许一个对象将其状态的改变通知其他对象,观察者模式提供了一种作用于任何实现了订阅者接口的对象的机制, 可对其事件进行订阅和取消订阅,本文就通过代码示例给大家详细介绍一下Go的观察者模式,需要的朋友可以参考下
    2023-07-07

最新评论