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

创建Stream实例


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

  1. API对象的Stream实例池中Borrow一个 ```go c := jsoniter.ConfigDefault s := c.BorrowStream(os.Stdout) defer c.ReturnStream(s)

// 你的功能实现 // xxxxxx // ……

  1. 使用这种方法"借用"`Stream`实例,记得在使用完毕后"返还"回去
  2. 2. 调用`NewStream`接口新建一个
  3. ```go
  4. s := jsoniter.NewStream(jsoniter.ConfigDefault, os.Stdout, 1024)
  5. // 你的功能实现
  6. // xxxxxx
  7. // ......

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

定制编码输出


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

  1. type sampleExtension struct {
  2. jsoniter.DummyExtension
  3. }
  4. type wrapEncoder struct {
  5. encodeFunc func(ptr unsafe.Pointer, stream *jsoniter.Stream)
  6. isEmptyFunc func(ptr unsafe.Pointer) bool
  7. }
  8. func (enc *wrapEncoder) Encode(ptr unsafe.Pointer, stream *jsoniter.Stream) {
  9. enc.encodeFunc(ptr, stream)
  10. }
  11. func (enc *wrapEncoder) IsEmpty(ptr unsafe.Pointer) bool {
  12. if enc.isEmptyFunc == nil {
  13. return false
  14. }
  15. return enc.isEmptyFunc(ptr)
  16. }
  17. func (e *sampleExtension) CreateEncoder(typ reflect2.Type) jsoniter.ValEncoder {
  18. if typ.Kind() == reflect.Int {
  19. return &wrapEncoder{
  20. func(ptr unsafe.Pointer, stream *jsoniter.Stream) {
  21. // 将Int类型的变量的值+1000后,再写入到输出的json
  22. stream.WriteInt(*(*int)(ptr) + 1000)
  23. },
  24. nil,
  25. }
  26. }
  27. return nil
  28. }
  29. func streamTest(){
  30. jsoniter.RegisterExtension(&sampleExtension{})
  31. j, _ := jsoniter.MarshalToString(1000)
  32. fmt.Println(j)
  33. // 输出:2000
  34. }

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

  1. type testStructForStream struct{
  2. Field int
  3. }
  4. jsoniter.RegisterFieldEncoderFunc(reflect2.TypeOf(testStructForStream{}).String(), "Field",
  5. func(ptr unsafe.Pointer, stream *jsoniter.Stream) {
  6. // 将Int类型的值转换成字符串类型的json输出
  7. stream.WriteString(strconv.Itoa(*(*int)(ptr)))
  8. }, nil)
  9. j, _ := jsoniter.MarshalToString(testStructForStream{1024})
  10. fmt.Println(j)
  11. // 输出:{"Field":"1024"}

这个例子里面,我们针对testStructForStreamField字段注册了一个ValEncoder,这个ValEncoder调用StreamWriteString方法,将ptr指向的Int类型数值以字符串的方式写入到json串
Stream开放了各种类型数据的写入方法,可以让我们很方便地去定制自己的数据以何种方式输出成json:

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

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

手动构造json输出


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

  1. s := jsoniter.ConfigDefault.BorrowStream(nil)
  2. // 记得把从Config中borrow过来的Stream实例Return回去
  3. defer jsoniter.ConfigDefault.ReturnStream(s)
  4. s.WriteObjectStart()
  5. s.WriteObjectField("EmbedStruct")
  6. s.WriteObjectStart()
  7. s.WriteObjectField("Name")
  8. s.WriteString("xxx")
  9. s.WriteObjectEnd()
  10. s.WriteMore()
  11. s.WriteObjectField("Id")
  12. s.WriteInt(100)
  13. s.WriteObjectEnd()
  14. fmt.Println(string(s.Buffer()))
  15. // 输出:{"EmbedStruct":{"Name":"xxx"},"Id":100}

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

另一种序列化接口


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

  1. type testStructForStream struct{
  2. Field int
  3. }
  4. s := jsoniter.NewStream(jsoniter.ConfigDefault, nil, 1024)
  5. s.WriteVal(testStructForStream{300})
  6. result := s.Buffer()
  7. buf := make([]byte, len(result))
  8. copy(buf, result)
  9. fmt.Println(string(buf))
  10. // 输出:{"Field":300}

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

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

    复用Stream实例


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

  1. type testStructForStream struct{
  2. Field int
  3. }
  4. s := jsoniter.ConfigDefault.BorrowStream(os.Stdout)
  5. defer jsoniter.ConfigDefault.ReturnStream(s)
  6. s.WriteVal(testStructForStream{300})
  7. result := s.Buffer()
  8. tmp := make([]byte, len(result))
  9. copy(tmp, result)
  10. // xxxxxx
  11. // ......
  12. if s.Error != nil{
  13. return
  14. }
  15. // 记得重置你的Stream
  16. s.Reset(nil)
  17. s.WriteVal(testStructForStream{400})

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

Flush到输出设备


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

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