json-iterator/go是一个golang编解码库,主要用于比特序列与map/struct的互转,在使用过程中,我们可能需要对编解码过程进行处理,比如客户端发过来的请求体中某个字段为加密或编码过的,业务代码不想感知这种行为,如何在反序列化的过程中优雅地处理掉这种问题,json-iterator提供了Extension的机制,来满足一些复杂的编解码场景。
通过extendsion机制可实现下列能力:
- 定制化的codec
- 操作struct属性
下面根据官方文档介绍其使用方式。
ValEncoder/ValDecoder接口
在介绍Extension的使用之前,需要先介绍一下ValEncoder和ValDecoder,因为Extension的本质上就是针对不同的类型创建不同的ValEncoder和ValDecoder实现的。注意,ValEncoder/ValDecoder和json.Encoder/json.Decoder是不一样的概念,不要混淆了。
ValEncoder
type ValEncoder interface {
IsEmpty(ptr unsafe.Pointer) bool
Encode(ptr unsafe.Pointer, stream *Stream)
}
ValEncoder实际上是json-iterator内部用于针对某个类型的数据进行序列化编码的编码器,它的两个成员函数说明如下:
- Encode
Encode函数用于实现某个类型数据的编码,ptr是指向当前待编码数据的指针,stream提供不同的接口供使用者将各种类型的数据写入到输出设备(详见Stream章节)。那么,在这个函数里面,我们怎么实现编码呢?实际上,我们大部分时间做的,就是将ptr转换成这个ValEncoder对应的数据类型的指针,然后调用stream的接口,将ptr指向的数值进行编码输出
- IsEmpty
IsEmpty是跟omitempty这个tag相关的函数。我们都知道,在一个结构体里面,如果某个字段的tag带上了omitempty属性,那么当这个字段对应的"数值为空"时,这个字段在序列化时不会被编码输出。那么什么叫"数值为空"呢?对于不同类型的数据,恐怕应该是有不同的定义的。因此IsEmpty这个函数里面,就是需要你去实现,你的ValEncoder对应的数据类型在实际数值是什么的时候,称作"数值为空"
我们看一个具体的例子,来帮助我们理解ValEncoder。json-iterator提供了一个内置的TimeAsInt64Codec,来看看它的实现:
func (codec *timeAsInt64Codec) IsEmpty(ptr unsafe.Pointer) bool {
ts := *((*time.Time)(ptr))
return ts.UnixNano() == 0
}
func (codec *timeAsInt64Codec) Encode(ptr unsafe.Pointer, stream *jsoniter.Stream) {
ts := *((*time.Time)(ptr))
stream.WriteInt64(ts.UnixNano() / codec.precision.Nanoseconds())
}
Encode函数中,将ptr转换成指向time.Time类型的指针,然后对其解引用拿到了其指向的time.Time对象。接下来调用其成员函数计算出它对应的unix时间,最后调用stream的写入接口将这个int64的unix时间数值进行编码输出,这样就完成了将原本以对象方式输出的time.Time数值,转换成int64类型的unix时间输出
IsEmpty通过同样方式拿到ptr指向的time.Time对象,然后将time.Time类型"数值为空"定义为其转换出来的unix时间为0
ValDecoder
type ValDecoder interface {
Decode(ptr unsafe.Pointer, iter *Iterator)
}
ValEncoder实际上是json-iterator内部用于针对某个类型的数据进行反序列化解码的解码器,它的成员函数说明如下:
- Decode
Decode函数用于实现某个类型数据的解码,ptr是指向当前待写入数据的指针,iter提供不同的接口供使用者将各种类型的数据从输入源读入(详见Iterator章节)。那么,在这个函数里面,我们怎么实现解码呢?首先,我们调用iter提供的接口,从json串的输入源读入ValDecoder对应类型的数据,然后将ptr做一个强转,将其转换成指向ValDecoder对应类型的指针,然后将该指针指向的数据设置成我们通过iter接口读取出来的值
还是看TimeAsInt64Codec的例子
func (codec *timeAsInt64Codec) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) {
nanoseconds := iter.ReadInt64() * codec.precision.Nanoseconds()
*((*time.Time)(ptr)) = time.Unix(0, nanoseconds)
}
Decode函数中,调用iter的接口从json输入源中读取了一个int64的数值,接下来因为我们这个ValDecoder对应的数据类型是time.Time,这里把ptr转换成指向time.Time类型的指针,并以我们读入的int64数值为unix时间初始化了一个time.Time对象,最后将它赋给ptr指向的数值。这样,我们就完成了从json串中读入unix时间,并将其转换成time.Time对象的功能
定制你的扩展
要定制序列化/反序列化扩展,需要实现Extension接口,并通过RegisterExtension进行注册,Extension包含以下方法:
type Extension interface {
UpdateStructDescriptor(structDescriptor *StructDescriptor)
CreateMapKeyDecoder(typ reflect2.Type) ValDecoder
CreateMapKeyEncoder(typ reflect2.Type) ValEncoder
CreateDecoder(typ reflect2.Type) ValDecoder
CreateEncoder(typ reflect2.Type) ValEncoder
DecorateDecoder(typ reflect2.Type, decoder ValDecoder) ValDecoder
DecorateEncoder(typ reflect2.Type, encoder ValEncoder) ValEncoder
}
当然,很多情况下,我们只需要用到里面的部分功能。json-iterator里面提供了一个DummyExtension,它是一个最基础的Extension实现(基本什么都不做或返回空)。当你在定义自己的Extension时,你可以匿名地嵌入DummyExtension,这样你就不需要实现所有的Extension成员,只需要关注自己需要的功能。下面我们通过一些例子,来说明Extension的各个成员函数可以用来做什么
- UpdateStructDescriptor
UpdateStructDescriptor函数中,我们可以对结构体的某个字段定制其编码/解码器,或者控制该字段序列化/反序列化时与哪些字符串绑定
type testCodec struct{
}
func (codec *testCodec) Encode(ptr unsafe.Pointer, stream *jsoniter.Stream){
str := *((*string)(ptr))
stream.WriteString("TestPrefix_" + str)
}
func (codec *testCodec) IsEmpty(ptr unsafe.Pointer) bool {
str := *((*string)(ptr))
return str == ""
}
type sampleExtension struct {
jsoniter.DummyExtension
}
func (e *sampleExtension) UpdateStructDescriptor(structDescriptor *jsoniter.StructDescriptor) {
// 这个判断保证我们只针对testStruct结构体,对其他类型无效
if structDescriptor.Type.String() != "main.testStruct" {
return
}
binding := structDescriptor.GetField("TestField")
binding.Encoder = &testCodec{}
binding.FromNames = []string{"TestField", "Test_Field", "Test-Field"}
}
func extensionTest(){
type testStruct struct {
TestField string
}
t := testStruct{"fieldValue"}
jsoniter.RegisterExtension(&sampleExtension{})
s, _ := jsoniter.MarshalToString(t)
fmt.Println(s)
// Output:
// {"TestField":"TestPrefix_fieldValue"}
jsoniter.UnmarshalFromString(`{"Test-Field":"bbb"}`, &t)
fmt.Println(t.TestField)
// Output:
// bbb
}
上面的例子,首先我们用testCodec实现了一个ValEncoder,它编码时在字符串的前面加了一个"TestPrefix"的前缀再输出。接着我们注册了一个sampleExtension,在UpdateStructDescriptor函数中我们将testStruct的TestField字段的编码器设置为我们的testCodec,最后将其与几个别名字符串进行了绑定。得到的效果就是,这个结构体序列化输出时,TestField的内容会添加上"TestPrefix"前缀;而反序列化时,TestField的别名都将映射成这个字段
-
CreateDecoder
-
CreateEncoder
CreateDecoder和CreateEncoder分别用来创建某个数据类型对应的解码器/编码器
type wrapCodec struct{
encodeFunc func(ptr unsafe.Pointer, stream *jsoniter.Stream)
isEmptyFunc func(ptr unsafe.Pointer) bool
decodeFunc func(ptr unsafe.Pointer, iter *jsoniter.Iterator)
}
func (codec *wrapCodec) Encode(ptr unsafe.Pointer, stream *jsoniter.Stream) {
codec.encodeFunc(ptr, stream)
}
func (codec *wrapCodec) IsEmpty(ptr unsafe.Pointer) bool {
if codec.isEmptyFunc == nil {
return false
}
return codec.isEmptyFunc(ptr)
}
func (codec *wrapCodec) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) {
codec.decodeFunc(ptr, iter)
}
type sampleExtension struct {
jsoniter.DummyExtension
}
func (e *sampleExtension) CreateDecoder(typ reflect2.Type) jsoniter.ValDecoder {
if typ.Kind() == reflect.Int {
return &wrapCodec{
decodeFunc:func(ptr unsafe.Pointer, iter *jsoniter.Iterator) {
i := iter.ReadInt()
*(*int)(ptr) = i - 1000
},
}
}
return nil
}
func (e *sampleExtension) CreateEncoder(typ reflect2.Type) jsoniter.ValEncoder {
if typ.Kind() == reflect.Int {
return &wrapCodec{
encodeFunc:func(ptr unsafe.Pointer, stream *jsoniter.Stream) {
stream.WriteInt(*(*int)(ptr) + 1000)
},
isEmptyFunc:nil,
}
}
return nil
}
func extensionTest(){
i := 20000
jsoniter.RegisterExtension(&sampleExtension{})
s, _ := jsoniter.MarshalToString(i)
fmt.Println(s)
// Output:
// 21000
jsoniter.UnmarshalFromString(`30000`, &i)
fmt.Println(i)
// Output:
// 29000
}
上面的例子我们用wrapCodec实现了ValEncoder和ValDecoder,然后我们注册了一个Extension,这个Extension的CreateEncoder函数中设置了wrapCodec的Encode函数,指定对于Int类型的数值+1000后输出;CreateDecoder函数中设置了wrapCodec的Decode函数,指定读取了Int类型的数值后,-1000再进行赋值。这里要注意的是,不管是CreateEncoder还是CreateDecoder函数,我们都通过其typ参数限定了这个编码/解码器只对Int类型生效
-
CreateMapKeyDecoder
-
CreateMapKeyEncoder
CreateMapKeyDecoder和CreateMapKeyEncoder跟上面的CreateDecoder和CreateEncoder用法差不多,只不过他们的生效对象是map类型的key的,这里不再举例详述了。
-
DecorateDecoder
-
DecorateEncoder
DecorateDecoder和DecorateEncoder可以用于装饰现有的ValEncoder和ValEncoder。考虑这么一个例子,在上述的CreateDecoder和CreateEncoder的说明中所举例的基础上,我们想再做一层扩展。当我们遇到数字字符串时,我们希望也可以解析成整形数,并且要复用基础例子中的解码器,这时候我们就需要用到装饰器。
type decorateExtension struct{
jsoniter.DummyExtension
}
type decorateCodec struct{
originDecoder jsoniter.ValDecoder
}
func (codec *decorateCodec) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) {
if iter.WhatIsNext() == jsoniter.StringValue {
str := iter.ReadString()
if _, err := strconv.Atoi(str); err == nil{
newIter := iter.Pool().BorrowIterator([]byte(str))
defer iter.Pool().ReturnIterator(newIter)
codec.originDecoder.Decode(ptr, newIter)
}else{
codec.originDecoder.Decode(ptr, iter)
}
} else {
codec.originDecoder.Decode(ptr, iter)
}
}
func (e *decorateExtension) DecorateDecoder(typ reflect2.Type, decoder jsoniter.ValDecoder) jsoniter.ValDecoder{
if typ.Kind() == reflect.Int {
return &decorateCodec{decoder}
}
return nil
}
func extensionTest(){
var i int
jsoniter.RegisterExtension(&sampleExtension{})
jsoniter.RegisterExtension(&decorateExtension{})
jsoniter.UnmarshalFromString(`30000`, &i)
fmt.Println(i)
// Output:
// 29000
jsoniter.UnmarshalFromString(`"40000"`, &i)
fmt.Println(i)
// Output:
// 39000
}
在CreateDecoder和CreateEncoder的例子基础上,我们在注册一个Extension,这个Extension只实现了装饰器功能,它兼容字符串类型数字的解析,并且解析出来的数字依然要-1000再赋值
作用域
json-iterator有两个RegisterExtension接口可以调用,一个是package级别的jsoniter.RegisterExtension,一个是API(说明见Config章节)级别的API.RegisterExtension。这两个函数都可以用来注册扩展,但是两种注册方式注册的扩展的作用域略有不同。jsoniter.RegisterExtension注册的扩展,对于所有Config生成的API都生效;而API.RegisterExtension只对其对应的Config生成的API接口生效,这个需要注意