Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Iterator_cn

Allen edited this page May 28, 2020 · 1 revision

English | 中文

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

创建Iterator实例

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

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

    c := jsoniter.ConfigDefault
    i := c.BorrowIterator([]byte(`{"A":"a"}`))
    defer c.ReturnIterator(i)
    // 你的功能实现
    // xxxxxx
    // ......

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

  2. 调用NewIterator接口新建一个

    i := jsoniter.NewIterator(jsoniter.ConfigDefault)
    i.Reset(os.Stdin)
    // 或者i.ResetBytes(`{"A":"a"}`)
    // 你的功能实现
    // xxxxxx
    // ......

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

  3. 调用ParseXXX方法新建一个

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

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

定制解析行为

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

type testStructForIterator struct{
 BoolField bool
}
jsoniter.RegisterFieldDecoder(reflect2.TypeOf(testStructForIterator{}).String(), "BoolField",
 &wrapDecoder{
 func(ptr unsafe.Pointer, iter *jsoniter.Iterator) {
 typ := iter.WhatIsNext()
 switch typ {
 case jsoniter.BoolValue:
 *((*bool)(ptr)) = iter.ReadBool()
 case jsoniter.NumberValue:
 number := iter.ReadNumber()
 if n, err := number.Int64(); err == nil{
 if n > 0{
 *((*bool)(ptr)) = true
 }else{
 *((*bool)(ptr)) = false
 }
 }else{
 *((*bool)(ptr)) = false
 }
 case jsoniter.StringValue:
 str := iter.ReadString()
 if str == "true"{
 *((*bool)(ptr)) = true
 }else{
 *((*bool)(ptr)) = false
 }
 case jsoniter.NilValue:
 iter.ReadNil()
 *((*bool)(ptr)) = false
 default:
 iter.ReportError("wrapDecoder", "unknown value type")
 }
 },
 })
t := testStructForIterator{}
if err := jsoniter.Unmarshal([]byte(`{"BoolField":true}`), &t); err == nil{
 fmt.Println(t.BoolField)
 // 输出:true
}
if err := jsoniter.Unmarshal([]byte(`{"BoolField":1}`), &t); err == nil{
 fmt.Println(t.BoolField)
 // 输出:true
}
if err := jsoniter.Unmarshal([]byte(`{"BoolField":"true"}`), &t); err == nil{
 fmt.Println(t.BoolField)
 // 输出:true
}
if err := jsoniter.Unmarshal([]byte(`{"BoolField":"false"}`), &t); err == nil{
 fmt.Println(t.BoolField)
 // 输出:false
}
if err := jsoniter.Unmarshal([]byte(`{"BoolField":null}`), &t); err == nil{
 fmt.Println(t.BoolField)
 // 输出:false
}

在上面这个例子里面,我们针对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

jsonStr := `
{
	"_id": "58451574858913704731",
	"about": "a4KzKZRVvqfBLdnpUWaD",
	"address": "U2YC2AEVn8ab4InRwDmu",
	"age": 27,
	"balance": "I5cZ5vRPmVXW0lhhRzF4",
	"company": "jwLot8sFN1hMdE4EVW7e",
	"email": "30KqJ0oeYXLqhKMLDUg6",
	"eyeColor": "RWXrMsO6xi9cpxPqzJA1",
	"favoriteFruit": "iyOuAekbybTUeDJqkHNI",
	"gender": "ytgB3Kzoejv1FGU6biXu",
	"greeting": "7GXmN2vMLcS2uimxGQgC",
	"guid": "bIqNIywgrzva4d5LfNlm",
	"index": 169390966,
	"isActive": true,
	"latitude": 70.7333712683406,
	"longitude": 16.25873969455544,
	"name": "bvtukpT6dXtqfbObGyBU",
	"phone": "UsxtI7sWGIEGvM2N1Mh0",
	"picture": "8fiyZ2oKapWtH5kXyNDZJjvRS5PGzJGGxDCAk1he1wuhUjxfjtGIh6agQMbjovF10YlqOyzhQPCagBZpW41r6CdrghVfgtpDy7YH",
	"registered": "gJDieuwVu9H7eYmYnZkz",
	"tags": [
		"M2b9n0QrqC",
		"zl6iJcT68v",
		"VRuP4BRWjs",
		"ZY9jXIjTMR"
	]
}
`
fieldList := make([]string, 0)
iter := jsoniter.ParseString(jsoniter.ConfigDefault, jsonStr)
iter.ReadObjectCB(func(iter *jsoniter.Iterator, field string) bool{
 fieldList = append(fieldList, field)
 iter.Skip()
 return true
})
fmt.Println(fieldList)
// 输出:[_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方法基本一样的序列化功能

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

在上面这个例子里面,我们调用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实例

type testStructForIterator struct{
 Name string
 Id int
}
var dat testStructForIterator
iter := jsoniter.ParseString(jsoniter.ConfigDefault, `{"Name":"Allen","Id":100}`)
iter.ReadVal(&dat)
// xxxxxx
// ......
if iter.Error != nil{
 return
}
iter.ResetBytes([]byte(`{"Name":"Tom","Id":200}`))
iter.ReadVal(&dat)

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

Clone this wiki locally

AltStyle によって変換されたページ (->オリジナル) /