Skip to content

Extension_cn

Allen edited this page May 28, 2020 · 1 revision

English | 中文

Config可以提供部分选项来控制序列化/反序列化的行为,但是不能提供更精细的编码或解析控制,无法应对复杂的需求。json-iterator考虑了这一点,提供了Extension的机制,来满足复杂的序列化/反序列化场景。

ValEncoder/ValDecoder接口

在介绍Extension的使用之前,需要先介绍一下ValEncoderValDecoder,因为Extension的本质上就是针对不同的类型创建不同的ValEncoderValDecoder实现的。注意,ValEncoder/ValDecoderjson.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函数中我们将testStructTestField字段的编码器设置为我们的testCodec,最后将其与几个别名字符串进行了绑定。得到的效果就是,这个结构体序列化输出时,TestField的内容会添加上"TestPrefix_"前缀;而反序列化时,TestField的别名都将映射成这个字段

  • CreateDecoder

  • CreateEncoder

    CreateDecoderCreateEncoder分别用来创建某个数据类型对应的解码器/编码器

    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实现了ValEncoderValDecoder,然后我们注册了一个Extension,这个ExtensionCreateEncoder函数中设置了wrapCodecEncode函数,指定对于Int类型的数值+1000后输出;CreateDecoder函数中设置了wrapCodecDecode函数,指定读取了Int类型的数值后,-1000再进行赋值。这里要注意的是,不管是CreateEncoder还是CreateDecoder函数,我们都通过其typ参数限定了这个编码/解码器只对Int类型生效

  • CreateMapKeyDecoder

  • CreateMapKeyEncoder

    CreateMapKeyDecoderCreateMapKeyEncoder跟上面的CreateDecoderCreateEncoder用法差不多,只不过他们的生效对象是map类型的key的,这里不再举例详述了。

  • DecorateDecoder

  • DecorateEncoder

    DecorateDecoderDecorateEncoder可以用于装饰现有的ValEncoderValEncoder。考虑这么一个例子,在上述的CreateDecoderCreateEncoder的说明中所举例的基础上,我们想再做一层扩展。当我们遇到数字字符串时,我们希望也可以解析成整形数,并且要复用基础例子中的解码器,这时候我们就需要用到装饰器。

    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
    }

    CreateDecoderCreateEncoder的例子基础上,我们在注册一个Extension,这个Extension只实现了装饰器功能,它兼容字符串类型数字的解析,并且解析出来的数字依然要-1000再赋值

作用域

json-iterator有两个RegisterExtension接口可以调用,一个是package级别的jsoniter.RegisterExtension,一个是API(说明见Config章节)级别的API.RegisterExtension。这两个函数都可以用来注册扩展,但是两种注册方式注册的扩展的作用域略有不同。jsoniter.RegisterExtension注册的扩展,对于所有Config生成的API都生效;而API.RegisterExtension只对其对应的Config生成的API接口生效,这个需要注意