Skip to content

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来使用(至少目前的实现必须这样)