json-iterator中使用Iterator来实现流式解析。通过其提供的API,我们可以控制json串的解析行为,我们可以对json串中与schema定义不一致的字段做兼容性的解析处理,也可以跳过我们不关心的json串中的片段

创建Iterator实例


有三种方法可以创建Iterator实例:

  1. API对象的Iterator实例池中Borrow一个 ``go c := jsoniter.ConfigDefault i := c.BorrowIterator([]byte({“A”:”a”}`)) defer c.ReturnIterator(i)

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

  1. 2.使用这种方法"借用"`Iterator`实例,记得在使用完毕后"返还"回去
  2. ```go
  3. i := jsoniter.NewIterator(jsoniter.ConfigDefault)
  4. i.Reset(os.Stdin)
  5. // 或者i.ResetBytes(`{"A":"a"}`)
  6. // 你的功能实现
  7. // xxxxxx
  8. // ......

使用这种方法,需要传入你的序列化配置对应生成的API对象。对于这种方法,要指定输入源io.Reader或输入json串都只能在创建了Iterator后,调用其重置方法ResetResetBytes来设置其待解析输入。如果要在创建的时候就指定输入源,可以用第三种方法
3.调用ParseXXX方法新建一个

  1. i := jsoniter.Parse(jsoniter.ConfigDefault, os.Stdin, 1024)
  2. // 或者 i := jsoniter.ParseBytes(jsoniter.ConfigDefault, []byte(`{"A":"a"}`))
  3. // 或者 i := jsoniter.ParseString(jsoniter.ConfigDefault, `{"A":"a"}`)
  4. // 你的功能实现
  5. // xxxxxx
  6. // ......

使用Parse族的方法,可以在创建Iterator的时候指定待解析json串的输入源。其中Parse方法还可以指定Iterator用于解析的内部缓冲的大小

定制解析行为


想象一个这样的场景:我们的数据结构schema中某个字段定义成了bool类型,但是我们接收到的json串中,该字段对应的值可能是bool类型,可能是int类型,还可能是string类型,我们需要对其做兼容性的解析处理,这时候Iterator(配合ExtensionValDecoder)就可以发挥作用了。

  1. type testStructForIterator struct{
  2. BoolField bool
  3. }
  4. jsoniter.RegisterFieldDecoder(reflect2.TypeOf(testStructForIterator{}).String(), "BoolField",
  5. &wrapDecoder{
  6. func(ptr unsafe.Pointer, iter *jsoniter.Iterator) {
  7. typ := iter.WhatIsNext()
  8. switch typ {
  9. case jsoniter.BoolValue:
  10. *((*bool)(ptr)) = iter.ReadBool()
  11. case jsoniter.NumberValue:
  12. number := iter.ReadNumber()
  13. if n, err := number.Int64(); err == nil{
  14. if n > 0{
  15. *((*bool)(ptr)) = true
  16. }else{
  17. *((*bool)(ptr)) = false
  18. }
  19. }else{
  20. *((*bool)(ptr)) = false
  21. }
  22. case jsoniter.StringValue:
  23. str := iter.ReadString()
  24. if str == "true"{
  25. *((*bool)(ptr)) = true
  26. }else{
  27. *((*bool)(ptr)) = false
  28. }
  29. case jsoniter.NilValue:
  30. iter.ReadNil()
  31. *((*bool)(ptr)) = false
  32. default:
  33. iter.ReportError("wrapDecoder", "unknown value type")
  34. }
  35. },
  36. })
  37. t := testStructForIterator{}
  38. if err := jsoniter.Unmarshal([]byte(`{"BoolField":true}`), &t); err == nil{
  39. fmt.Println(t.BoolField)
  40. // 输出:true
  41. }
  42. if err := jsoniter.Unmarshal([]byte(`{"BoolField":1}`), &t); err == nil{
  43. fmt.Println(t.BoolField)
  44. // 输出:true
  45. }
  46. if err := jsoniter.Unmarshal([]byte(`{"BoolField":"true"}`), &t); err == nil{
  47. fmt.Println(t.BoolField)
  48. // 输出:true
  49. }
  50. if err := jsoniter.Unmarshal([]byte(`{"BoolField":"false"}`), &t); err == nil{
  51. fmt.Println(t.BoolField)
  52. // 输出:false
  53. }
  54. if err := jsoniter.Unmarshal([]byte(`{"BoolField":null}`), &t); err == nil{
  55. fmt.Println(t.BoolField)
  56. // 输出:false
  57. }

在上面这个例子里面,我们针对testStructForIteratorBoolField字段注册了一个ValDecoder。在它的Decode方法中,我们先调用IteratorWhatIsNext方法,通过json串中下一个元素的类似,来决定调用Iterator的哪个方法来解析下一个数值,根据解析结果,设置ptr指向的bool类型的数据值。这样不管我们解析的json串中,BoolField字段实际使用布尔、数值或是字符串来表示,我们都可以做到兼容
Iterator开放了各种接口用于从输入中读入不同类型的数据:

  • ReadBool
  • ReadString
  • ReadInt
  • ReadFloat32
  • ReadMapCB
  • ReadObjectCB
  • ReadArrayCB
  • …..

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

跳过json片段


使用Iterator,我们可以跳过json串中的特定片段,只处理我们感兴趣的部分。考虑这么一个场景:我们接收到一个json串,这个json串中包含了一个对象,我们只想把这个对象的每个字段的字段名记录下来,至于字段对应的具体内容,我们不关心。为了实现这样的需求,我们需要用到Iterator

  1. jsonStr := `
  2. {
  3. "_id": "58451574858913704731",
  4. "about": "a4KzKZRVvqfBLdnpUWaD",
  5. "address": "U2YC2AEVn8ab4InRwDmu",
  6. "age": 27,
  7. "balance": "I5cZ5vRPmVXW0lhhRzF4",
  8. "company": "jwLot8sFN1hMdE4EVW7e",
  9. "email": "30KqJ0oeYXLqhKMLDUg6",
  10. "eyeColor": "RWXrMsO6xi9cpxPqzJA1",
  11. "favoriteFruit": "iyOuAekbybTUeDJqkHNI",
  12. "gender": "ytgB3Kzoejv1FGU6biXu",
  13. "greeting": "7GXmN2vMLcS2uimxGQgC",
  14. "guid": "bIqNIywgrzva4d5LfNlm",
  15. "index": 169390966,
  16. "isActive": true,
  17. "latitude": 70.7333712683406,
  18. "longitude": 16.25873969455544,
  19. "name": "bvtukpT6dXtqfbObGyBU",
  20. "phone": "UsxtI7sWGIEGvM2N1Mh0",
  21. "picture": "8fiyZ2oKapWtH5kXyNDZJjvRS5PGzJGGxDCAk1he1wuhUjxfjtGIh6agQMbjovF10YlqOyzhQPCagBZpW41r6CdrghVfgtpDy7YH",
  22. "registered": "gJDieuwVu9H7eYmYnZkz",
  23. "tags": [
  24. "M2b9n0QrqC",
  25. "zl6iJcT68v",
  26. "VRuP4BRWjs",
  27. "ZY9jXIjTMR"
  28. ]
  29. }
  30. `
  31. fieldList := make([]string, 0)
  32. iter := jsoniter.ParseString(jsoniter.ConfigDefault, jsonStr)
  33. iter.ReadObjectCB(func(iter *jsoniter.Iterator, field string) bool{
  34. fieldList = append(fieldList, field)
  35. iter.Skip()
  36. return true
  37. })
  38. fmt.Println(fieldList)
  39. // 输出:[_id about address age balance company email eyeColor favoriteFruit gender greeting guid index isActive latitude longitude name phone picture registered tags]

在上面的例子中,我们调用了ParseString来创建一个Iterator实例。ParseString可以指定Iterator实例对应的配置和作为解析源的json串。然后我们调用了IteratorReadObjectCB方法,调用时必须传入一个回调函数。ReadObjectCB方法会解析一个对象类型的json串,并迭代这个json串中的顶层对象的每个字段,对每个字段都会调用我们一开始传进去的回调函数。这里可以看到,在回调函数里面,我们只是将传进来的字段名记录下来,然后调用IteratorSkip来跳过这个字段对应的实际内容。Skip会自动解析json串中接下来的元素是什么类型的,然后跳过它的解析,跳到下一个字段。当遍历完毕后我们就可以拿到我们需要的字段列表了。

另一种反序列化接口


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

  1. type testStructForIterator struct{
  2. Name string
  3. Id int
  4. }
  5. var dat testStructForIterator
  6. iter := jsoniter.Parse(jsoniter.ConfigDefault, nil, 1024)
  7. iter.ResetBytes([]byte(`{"Name":"Allen","Id":100}`))
  8. if iter.ReadVal(&dat); iter.Error == nil || iter.Error == io.EOF{
  9. fmt.Println(dat)
  10. // 输出:{Allen 100}
  11. }

在上面这个例子里面,我们调用Parse来创建了一个Iterator实例,不设置输入设备io.Reader,我们用ResetBytes来设置待解析的json串,然后调用ReadVal方法来实现序列化。通过这种方式,也可以完成反序列化。实际上,json-iterator内部也是使用类似的方式,调用IteratorReadVal来完成反序列化。这里有一点需要说明:

  • 调用Parse创建Iterator实例,可以指定Iterator内部缓冲的大小。对于解析输入源从io.Reader读入的应用场合,由于Iterator的内部流式实现,是不会一次过将数据从io.Reader全部读取出来然后解析的,而是每次读入不超过缓冲区长度的大小的数据,然后解析。当解析过程发现缓冲区中数据已经解析完,又会从io.Reader中读取数据到缓冲区,继续解析,直至整个完整的json串解析完毕。考虑这么一个例子:你的Iterator的缓冲区大小设置为1024,但你的io.Reader里面有10M的json串需要解析,这样大概可以认为要把这个json串解析完,需要从io.Reader读入数据10240次,每次读1024字节。因此,如果你的解析源需要从io.Reader中读入,对性能要求较高,而对内存占用不太敏感,那么不妨放弃直接调用Unmarshal,自己创建Iterator来进行反序列化,并适当将Iterator的缓冲设置得大一点,提高解析效率

    复用Iterator实例


你可以调用Reset(解析源为io.Reader)或者ResetBytes(解析源为字符串或字节序列)来复用你的Iterator实例

  1. type testStructForIterator struct{
  2. Name string
  3. Id int
  4. }
  5. var dat testStructForIterator
  6. iter := jsoniter.ParseString(jsoniter.ConfigDefault, `{"Name":"Allen","Id":100}`)
  7. iter.ReadVal(&dat)
  8. // xxxxxx
  9. // ......
  10. if iter.Error != nil{
  11. return
  12. }
  13. iter.ResetBytes([]byte(`{"Name":"Tom","Id":200}`))
  14. iter.ReadVal(&dat)

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