golang通过json-iterator提供的扩展点,实现json编解码过程可定制

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接口生效,这个需要注意

留下评论

您的电子邮箱地址不会被公开。 必填项已用*标注