Skip to content

Stream_cn

Allen edited this page May 28, 2020 · 1 revision

English | 中文

json-iterator中使用Stream来控制json的编码输出,通过其提供的API,配合自定义的ExtensionValEncoder,我们可以定制我们的数据如何编码输出成json,甚至可以从头构造并输出一个json串

创建Stream实例

有两种方法可以创建Stream实例:

  1. API对象的Stream实例池中Borrow一个

    c := jsoniter.ConfigDefault
    s := c.BorrowStream(os.Stdout)
    defer c.ReturnStream(s)
    
    // 你的功能实现
    // xxxxxx
    // ......

    使用这种方法"借用"的Stream实例,记得在使用完毕后"返还"回去

  2. 调用NewStream接口新建一个

    s := jsoniter.NewStream(jsoniter.ConfigDefault, os.Stdout, 1024)
    
    // 你的功能实现
    // xxxxxx
    // ......

    使用这种方法,需要传入你的序列化配置对应生成的API对象,底层输出的io.Writer和指定Stream的内部缓冲内部大小(见下文详述)

定制编码输出

在定义你的ExtensionValEncoder时,你需要用到Stream来定制你的字段如何输出成json

type sampleExtension struct {
    jsoniter.DummyExtension
}

type wrapEncoder struct {
    encodeFunc  func(ptr unsafe.Pointer, stream *jsoniter.Stream)
    isEmptyFunc func(ptr unsafe.Pointer) bool
}

func (enc *wrapEncoder) Encode(ptr unsafe.Pointer, stream *jsoniter.Stream) {
    enc.encodeFunc(ptr, stream)
}

func (enc *wrapEncoder) IsEmpty(ptr unsafe.Pointer) bool {
    if enc.isEmptyFunc == nil {
        return false
    }

    return enc.isEmptyFunc(ptr)
}

func (e *sampleExtension) CreateEncoder(typ reflect2.Type) jsoniter.ValEncoder {
    if typ.Kind() == reflect.Int {
        return &wrapEncoder{
            func(ptr unsafe.Pointer, stream *jsoniter.Stream) {
                // 将Int类型的变量的值+1000后,再写入到输出的json
                stream.WriteInt(*(*int)(ptr) + 1000)
            },
            nil,
        }
    }

    return nil
}

func streamTest(){
    jsoniter.RegisterExtension(&sampleExtension{})
    j, _ := jsoniter.MarshalToString(1000)
    fmt.Println(j)
    // 输出:2000
}

在上面的例子中,我们注册了一个Extension,这个ExtensionCreateEncoder函数中,我们调用了StreamWriteInt接口,来将ptr指向的数值加1000后,再输出成json;在自定义ValEncoder中,我们同样使用Stream提供的函数来定制我们字段的输出

type testStructForStream struct{
    Field int
}

jsoniter.RegisterFieldEncoderFunc(reflect2.TypeOf(testStructForStream{}).String(), "Field",
func(ptr unsafe.Pointer, stream *jsoniter.Stream) {
    // 将Int类型的值转换成字符串类型的json输出
    stream.WriteString(strconv.Itoa(*(*int)(ptr)))
}, nil)

j, _ := jsoniter.MarshalToString(testStructForStream{1024})
fmt.Println(j)
// 输出:{"Field":"1024"}

这个例子里面,我们针对testStructForStreamField字段注册了一个ValEncoder,这个ValEncoder调用StreamWriteString方法,将ptr指向的Int类型数值以字符串的方式写入到json串

Stream开放了各种类型数据的写入方法,可以让我们很方便地去定制自己的数据以何种方式输出成json:

  • WriteBool
  • WriteInt
  • WriteFloat32
  • WriteString
  • WriteArrayStartWriteArrayEnd
  • WriteObjectStartWriteObjectEnd
  • WriteEmptyArray
  • WriteEmptyObject
  • ......

具体每个方法的说明可以参考godoc

手动构造json输出

使用Stream,可以完全手动地构造你的json如何输出成字节流

s := jsoniter.ConfigDefault.BorrowStream(nil)
// 记得把从Config中borrow过来的Stream实例Return回去
defer jsoniter.ConfigDefault.ReturnStream(s)

s.WriteObjectStart()
s.WriteObjectField("EmbedStruct")
s.WriteObjectStart()
s.WriteObjectField("Name")
s.WriteString("xxx")
s.WriteObjectEnd()
s.WriteMore()
s.WriteObjectField("Id")
s.WriteInt(100)
s.WriteObjectEnd()

fmt.Println(string(s.Buffer()))
// 输出:{"EmbedStruct":{"Name":"xxx"},"Id":100}

不过一般情况下,我们不会也不需要这么做,更多的时候是创建自己的ExtensionValEncoder时调用Stream的这些方法来定制编码输出

另一种序列化接口

Stream也提供了一个接口,可以实现跟EncoderEncode方法基本一样的序列化功能

type testStructForStream struct{
    Field int
}

s := jsoniter.NewStream(jsoniter.ConfigDefault, nil, 1024)
s.WriteVal(testStructForStream{300})
result := s.Buffer()
buf := make([]byte, len(result))
copy(buf, result)
fmt.Println(string(buf))
// 输出:{"Field":300}

在上面这个例子里面,我们调用NewStream来创建了一个Stream实例,然后调用WriteVal方法来实现序列化,最后将结果字节序列拷贝出来。通过这种方式,也可以完成序列化。实际上,json-iterator内部也是使用类似的方式,调用StreamWriteVal来完成序列化。这里有两点需要说明:

  • 调用NewStream创建Stream实例,可以指定Stream内部缓冲的大小。如果你的使用场景对性能有极致要求,而且序列化输出的json序列长度可以准确估计的话,不妨使用这个方法来取代直接调用Marshal来进行序列化。通过指定内部缓冲的初始大小,避免后续在序列化过程中发生的扩容(Stream内部存储序列化结果的缓存大小由其指定)
  • 上述例子中的拷贝操作是必须要进行的,不能直接调用StreamBuffer方法后返回的切片直接使用。因为Stream会复用其内部已经分配的缓冲,每次序列化都会把之前的内容复写掉,因此在下一次调用同一个Stream实例进行序列化前,你必须把结果拷贝走

复用Stream实例

如果你需要复用同一个Stream实例,记得在每次序列化完成后,重置你的Stream

type testStructForStream struct{
    Field int
}

s := jsoniter.ConfigDefault.BorrowStream(os.Stdout)
defer jsoniter.ConfigDefault.ReturnStream(s)
s.WriteVal(testStructForStream{300})
result := s.Buffer()
tmp := make([]byte, len(result))
copy(tmp, result)

// xxxxxx
// ......

if s.Error != nil{
    return
}

// 记得重置你的Stream
s.Reset(nil)
s.WriteVal(testStructForStream{400})

请注意,如果你的Stream在序列化过程中出现了错误,即Stream.Error不为nil,那么你不能继续使用这个Stream实例进行新的序列化或编码输出,即使你调了Reset进行重置也不行,只能重新另外创建一个新的Stream来使用(至少目前的实现必须这样)

Flush到输出设备

如果你在创建Stream时,指定了使用的io.Writer,并希望你序列化后的json写入到这里,而不是通过调用Buffer来获取结果的话,记得在序列化结束的时候调用Flush来将Stream中的缓冲刷到你的io.Writer

type testStructForStream struct{
    Field int
}

s := jsoniter.NewStream(jsoniter.ConfigDefault, os.Stdout, 1024)
s.WriteVal(testStructForStream{300})
// 如果没有这个调用,你的序列化结果将不会输出到你指定的Writer
s.Flush()
// 输出:{"Field":300}