-
Notifications
You must be signed in to change notification settings - Fork 1k
Iterator_cn
English | 中文
json-iterator中使用Iterator
来实现流式解析。通过其提供的API,我们可以控制json串的解析行为,我们可以对json串中与schema定义不一致的字段做兼容性的解析处理,也可以跳过我们不关心的json串中的片段
有三种方法可以创建Iterator
实例:
-
从
API
对象的Iterator
实例池中Borrow一个c := jsoniter.ConfigDefault i := c.BorrowIterator([]byte(`{"A":"a"}`)) defer c.ReturnIterator(i) // 你的功能实现 // xxxxxx // ......
使用这种方法"借用"的
Iterator
实例,记得在使用完毕后"返还"回去 -
调用
NewIterator
接口新建一个i := jsoniter.NewIterator(jsoniter.ConfigDefault) i.Reset(os.Stdin) // 或者i.ResetBytes(`{"A":"a"}`) // 你的功能实现 // xxxxxx // ......
使用这种方法,需要传入你的序列化配置对应生成的
API
对象。对于这种方法,要指定输入源io.Reader
或输入json串都只能在创建了Iterator
后,调用其重置方法Reset
或ResetBytes
来设置其待解析输入。如果要在创建的时候就指定输入源,可以用第三种方法 -
调用
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
(配合Extension
或ValDecoder
)就可以发挥作用了。
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
}
在上面这个例子里面,我们针对testStructForIterator
的BoolField
字段注册了一个ValDecoder
。在它的Decode
方法中,我们先调用Iterator
的WhatIsNext
方法,通过json串中下一个元素的类似,来决定调用Iterator
的哪个方法来解析下一个数值,根据解析结果,设置ptr
指向的bool
类型的数据值。这样不管我们解析的json串中,BoolField
字段实际使用布尔、数值或是字符串来表示,我们都可以做到兼容
Iterator
开放了各种接口用于从输入中读入不同类型的数据:
ReadBool
ReadString
ReadInt
ReadFloat32
ReadMapCB
ReadObjectCB
ReadArrayCB
- ......
具体每个方法的说明可以参考godoc
使用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串。然后我们调用了Iterator
的ReadObjectCB
方法,调用时必须传入一个回调函数。ReadObjectCB
方法会解析一个对象类型的json串,并迭代这个json串中的顶层对象的每个字段,对每个字段都会调用我们一开始传进去的回调函数。这里可以看到,在回调函数里面,我们只是将传进来的字段名记录下来,然后调用Iterator
的Skip
来跳过这个字段对应的实际内容。Skip
会自动解析json串中接下来的元素是什么类型的,然后跳过它的解析,跳到下一个字段。当遍历完毕后我们就可以拿到我们需要的字段列表了。
Iterator
也提供了一个接口,可以实现跟Decoder
的Decode
方法基本一样的序列化功能
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内部也是使用类似的方式,调用Iterator
的ReadVal
来完成反序列化。这里有一点需要说明:
- 调用
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
的缓冲设置得大一点,提高解析效率
你可以调用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
来使用(至少目前的实现必须这样)