最近接觸到了 [github.com/json-iterator/go](https://github.com/json-iterator/go) , 是由滴滴開源的第三方json編碼庫,它同時提供Go和Java兩個版本。 > 文中大量內容來自 github 上的 wiki 文檔,有興趣的朋友可以直 ...
最近接觸到了 github.com/json-iterator/go , 是由滴滴開源的第三方json編碼庫,它同時提供Go和Java兩個版本。
文中大量內容來自 github 上的 wiki 文檔,有興趣的朋友可以直接點擊 Home 跳轉到官方文檔查閱。
本文加了些自己的思考以及相關的詳細學習例子,廢話不多說了,沖!!!
1、基礎介紹
json-iterator提供簡潔的API,可以讓你很方便地進行json序列化/反序列化;與encoding/json完全相容,使用者可以快速、方便地遷移到json-iterator上來。此外,json-iterator還提供了很多其他方便的功能,如開放的序列化/反序列化配置、Extension、FieldEncoder/FieldDecoder、懶解析Any對象等等增強功能,應對不同使用場景下的json編碼和解析,滿足各種複雜的需求
1.1、簡單的API
-
序列化
type Student struct{ Name string Age int Height float32 } b, err := jsoniter.Marshal(Student{"Allen", 18, 180.43})
-
反序列化
type Student struct{ Name string Age int Height float32 } var std Student err := jsoniter.Unmarshal([]byte(`{"Name":"Allen","Age":18,"Height":180.43}`), &std)
1.2、替代encoding/json
encoding/json可以很方便地遷移到json-iterator,並且遷移前後代碼行為保持一致。不管你是使用基本的Marshal/Unmarshal介面,或是使用Encoder/Decoder,或是你已有的Marshaler/Unmarshaler實現,都能正常地工作。
這一點還是挺重要的,尤其是對於替換成該 json 庫的項目。
// import "encoding/json"
//
// json.Marshal(data)
import "github.com/json-iterator/go"
jsoniter.Marshal(data)
只需要把import的package替換成"github.com/json-iterator/go",包名從"json",替換成"jsoniter"即可
1.3、序列化/反序列化配置
json-iterator提供了幾種序列化/反序列化配置,供不同的場景下的使用
api := jsoniter.Config{SortMapKeys:true}.Froze()
b, err := api.Marshal(map[string]string{"C":"c", "A":"a", "B":"b"})
上面的例子中,我們開啟了SortMapKeys
配置選項,讓map序列化輸出時欄位進行排序輸出。更多的選項說明,請參考Config章節
1.4、控制編解碼行為
json-iterator提供了Extension
機制,我們可以通過註冊自己的Extension
,來更精確地控制我們的序列化/反序列化行為
type sampleExtension struct {
jsoniter.DummyExtension
}
func (e *sampleExtension) UpdateStructDescriptor(structDescriptor *jsoniter.StructDescriptor) {
if structDescriptor.Type.String() != "main.testStruct" {
return
}
binding := structDescriptor.GetField("TestField")
binding.FromNames = []string{"TestField", "Test_Field", "Test-Field"}
}
type testStruct struct {
TestField string
}
var t testStruct
jsoniter.RegisterExtension(&sampleExtension{})
err := jsoniter.Unmarshal([]byte(`{"TestField":"aaa"}`), &t)
err = jsoniter.Unmarshal([]byte(`{"Test_Field":"bbb"}`), &t)
err = jsoniter.Unmarshal([]byte(`{"Test-Field":"ccc"}`), &t)
上面的例子中我們註冊了一個Extension
,它指定了testStruct
的TestField
欄位名綁定到哪些字元串上,所有綁定的字元串在解析時都當成是該欄位。更多的說明請參考Extension章節
1.5、快速操作json對象
json-iterator提供了Any
對象,可以讓你schemaless地從複雜嵌套json串中提取出感興趣的部分
jsoniter.Get([]byte(`{"Field":{"InnerField":{"Name":"Allen"}}}`), "Field", "InnerField", "Name").ToString()
// output: Allen
這裡Get
返回的是一個Any
對象,我們獲取嵌套結構體最內層的Name
欄位的Any
對象,並調用ToString
轉換得到我們要的"Allen"字元串。更多的說明請參考Any章節
2、Config (重點)
下來內容全部來自官網,Config_cn 點擊上面鏈接即可跳轉。
json-iterator提供了一些常用的序列化/反序列化選型供配置,使用者可以根據自己需求打開/關閉特定的選項
2.1、配置選項
要配置序列化/反序列化選項,你需要創建一個Config
結構體,並通過設置其欄位來設置不同的選項,最後你還需要調用其Froze
方法來生成這個Config
對應的API
對象,通過這個API
對象來調用你的配置選項對應的序列化/反序列化函數。
api := jsoniter.Config{SortMapKeys:true}.Froze()
api.Marshal(data)
這裡我們創建了一個開啟了SortMapKeys
選項的Config
,並生成其對應的API
對象,來進行序列化
2.2、內置配置
json-iterator提供了三個內置的配置:
-
ConfigDefault
ConfigDefault
是預設配置,它開啟了EscapeHTML
選項。預設配置的意思是說,當你不創建自己的Config
對象並生成API
對象,而是直接以類似jsoniter.xxx
的方式調用介面時,實際上你的序列化/反序列化使用的就是這個ConfigDefault
配置 -
ConfigCompatibleWithStandardLibrary
ConfigCompatibleWithStandardLibrary
開啟了EscapeHTML
、SortMapKeys
和ValidateJsonRawMessage
選項,當你需要近似100%地保證你的序列化/反序列化行為與encoding/json
保持一致時,你可以使用這個配置 -
ConfigFastest
ConfigFastest
關閉了EscapeHTML
,開啟了MarshalFloatWith6Digits
和ObjectFieldMustBeSimpleString
選項,這個配置可以讓你的序列化/反序列化達到最高效率,但會有某些限制
2.3、選項說明
-
IndentionStep
指定格式化序列化輸出時的空格縮進數量
type Student struct{ Name string Age int Height float32 } // 四空格縮進的格式化輸出 c := jsoniter.Config{IndentionStep:4}.Froze() if s, err := c.MarshalToString(Student{"Allen", 18, 180.43}); err == nil{ fmt.Println(s) // Output: // { // "Name": "Allen", // "Age": 18, // "Height": 180.43 // } }
-
MarshalFloatWith6Digits
指定浮點數序列化輸出時最多保留6位小數
c := jsoniter.Config{MarshalFloatWith6Digits:true}.Froze() if s, err := c.MarshalToString(3.14159265358979); err == nil{ fmt.Println(s) // Output: // 3.141593 }
-
EscapeHTML
開啟了這個選項後,如果你的
string
類型的變數中含有HTML中使用的特殊字元(如'<','>','&'等),序列化時它們會被轉義輸出type Text struct{ Html string } c := jsoniter.Config{EscapeHTML:true}.Froze() if s, err := c.MarshalToString(Text{`<script>xxx</script>`}); err == nil{ fmt.Println(s) // Output: // {"Html":"\u003cscript\u003exxx\u003c/script\u003e"} }
-
SortMapKeys
指定
map
類型序列化輸出時按照其key排序rgb := map[string][3]int{ "yellow":{255, 255, 0}, "red":{0, 0, 255}, "green":{0, 255, 0}, "blue":{0, 0, 255}, } c := jsoniter.Config{SortMapKeys:true}.Froze() if s, err := c.MarshalToString(rgb); err == nil{ fmt.Println(s) // 按key的字典序排序輸出 // Output: // {"blue":[0,0,255],"green":[0,255,0],"red":[0,0,255],"yellow":[255,255,0]} }
-
UseNumber
指定反序列化時將數字(整數、浮點數)解析成
json.Number
類型。var number interface{} c := jsoniter.Config{UseNumber:true}.Froze() if err := c.UnmarshalFromString(`3.14159265358979`, &number); err == nil{ if n, ok := number.(json.Number); ok{ // 數字被解析成json.Number類型 f, _ := n.Float64() fmt.Println(f) // Output: // 3.14159265358979 } }
-
DisallowUnknownFields
當開啟該選項時,反序列化過程如果解析到未知欄位,即在結構體的schema定義中找不到的欄位時,不會跳過然後繼續解析,而會返回錯誤
type Student struct{ Name string Age int Height float32 } var std Student c := jsoniter.Config{DisallowUnknownFields:true}.Froze() // json串中包含未知欄位"Weight" if err := c.UnmarshalFromString(`{"Name":"Allen","Age":18,"Height":180.43,"Weight":60.56}`, &std); err == nil{ fmt.Println(std) }else{ fmt.Println(err) // Output // main.Student.ReadObject: found unknown field: Weight, error found in #10 byte of ...|3,"Weight":60.56}|..., bigger context ...|{"Name":"Allen","Age":18,"Height":180.43,"Weight":60.56}|... }
-
TagKey
指定tag字元串,預設情況為"json",我們可以指定成另一個字元串
type Student struct{ Name string `jsoniter:"name"` Age int Height float32 `jsoniter:"-"` } // 將tag指定為"jsoniter" c := jsoniter.Config{TagKey:"jsoniter"}.Froze() if s, err := c.MarshalToString(Student{"Allen", 18, 180.43}); err == nil{ fmt.Println(s) // Output: // {"name":"Allen","Age":18} }
-
OnlyTaggedField
當開啟該選項時,只有帶上tag的結構體欄位才會被序列化輸出
type Student struct{ Name string `json:"name"` Age int Height float32 `json:",omitempty"` } c := jsoniter.Config{OnlyTaggedField:true}.Froze() if s, err := c.MarshalToString(Student{"Allen", 18, 180.43}); err == nil{ fmt.Println(s) // Age欄位沒有tag,不會編碼輸出 // Output: // {"name":"Allen","Height":180.43} }
-
ValidateJsonRawMessage
json.RawMessage
類型的欄位在序列化時會原封不動地進行輸出。開啟這個選項後,json-iterator會校驗這種類型的欄位包含的是否一個合法的json串,如果合法,原樣輸出;否則會輸出"null"type Book struct{ Pages int Name string Description json.RawMessage } c := jsoniter.Config{ValidateJsonRawMessage:true}.Froze() if s, err := c.MarshalToString(Book{361, "X",json.RawMessage(`{"Category":`)}); err == nil{ fmt.Println(s) // Description 欄位為非法json串,輸出null // Output: // {"Pages":361,"Name":"X","Description":null} }
-
ObjectFieldMustBeSimpleString
開啟該選項後,反序列化過程中不會對你的json串中對象的欄位字元串可能包含的轉義進行處理,因此你應該保證你的待解析json串中對象的欄位應該是簡單的字元串(不包含轉義)
type Student struct{ Name string Age int Height float32 } var std Student c := jsoniter.Config{ObjectFieldMustBeSimpleString:true}.Froze() if err := c.UnmarshalFromString(`{"Name":"Allen","Ag\u0065":18,"Height":180.43}`, &std); err == nil{ fmt.Println(std) // Age欄位的轉義不會處理,因此該欄位無法解析出來 // Output: // {Allen 0 180.43} }
-
CaseSensitive
開啟該選項後,你的待解析json串中的對象的欄位必須與你的schema定義的欄位大小寫嚴格一致
type Student struct{ Name string Age int Height float32 } var std Student c := jsoniter.Config{CaseSensitive:true}.Froze() if err := c.UnmarshalFromString(`{"Name":"Allen","Age":18,"height":180.43}`, &std); err == nil{ fmt.Println(std) // Height欄位的大小寫不一致,無法解析出來 // Output: // {Allen 18 0} }
我們知道,當我們為為某個類型實現了
MarshalJSON()([]byte, error)和
UnmarshalJSON(b []byte) error方法,那麼這個類型在序列化(MarshalJSON)/反序列化(UnmarshalJSON)時就會使用你定製的相應方法。
知道了這個前提知識後,我們來一起看看如何自定義 序列化和反序列化方式。
3、Extension
Config
可以提供部分選項來控制序列化/反序列化的行為,但是不能提供更精細的編碼或解析控制,無法應對複雜的需求。json-iterator考慮了這一點,提供了Extension
的機制,來滿足複雜的序列化/反序列化場景。
3.1、ValEncoder/ValDecoder介面
在介紹Extension
的使用之前,需要先介紹一下ValEncoder
和ValDecoder
,因為Extension
的本質上就是針對不同的類型創建不同的ValEncoder
和ValDecoder
實現的。註意,ValEncoder
/ValDecoder
和json.Encoder
/json.Decoder
是不一樣的概念,不要混淆了。
-
ValEncoder
type ValEncoder interface { IsEmpty(ptr unsafe.Pointer) bool Encode(ptr unsafe.Pointer, stream *Stream) }
ValEncoder
實際上是json-iterator內部用於針對某個類型的數據進行序列化編碼的編碼器,它的兩個成員函數說明如下:-
Encode
Encode
函數用於實現某個類型數據的編碼,ptr
是指向當前待編碼數據的指針,stream
提供不同的介面供使用者將各種類型的數據寫入到輸出設備(詳見Stream章節)。那麼,在這個函數裡面,我們怎麼實現編碼呢?實際上,我們大部分時間做的,就是將ptr
轉換成這個ValEncoder
對應的數據類型的指針,然後調用stream
的介面,將ptr
指向的數值進行編碼輸出 -
IsEmpty
IsEmpty
是跟omitempty
這個tag相關的函數。我們都知道,在一個結構體裡面,如果某個欄位的tag帶上了omitempty
屬性,那麼當這個欄位對應的"數值為空"時,這個欄位在序列化時不會被編碼輸出。那麼什麼叫"數值為空"呢?對於不同類型的數據,恐怕應該是有不同的定義的。因此IsEmpty
這個函數裡面,就是需要你去實現,你的ValEncoder
對應的數據類型在實際數值是什麼的時候,稱作"數值為空"
我們看一個具體的例子,來幫助我們理解
ValEncoder
。json-iterator提供了一個內置的TimeAsInt64Codec
,來看看它的實現:func (codec *timeAsInt64Codec) IsEmpty(ptr unsafe.Pointer) bool { ts := *((*time.Time)(ptr)) return ts.UnixNano() == 0 } /* 在這種情況下,`ptr` 被聲明為 `unsafe.Pointer`,並且進行了類型轉換操作 `*((*time.Time)(ptr))`。讓我們逐步解釋這個表達式: 1. `unsafe.Pointer`:`unsafe.Pointer` 是 Go 語言中的一個特殊類型,它可以表示任何指針類型,但是使用它可能會繞過類型安全檢查,因此需要非常小心使用。 2. `(ptr)`:這裡的 `(ptr)` 將 `unsafe.Pointer` 類型轉換為 `*time.Time` 指針類型。這是一個類型斷言(type assertion),將一個指針從 `unsafe.Pointer` 轉換為 `*time.Time` 類型的指針。 3. `*((*time.Time)(ptr))`:外層的 `*` 表示這是一個指針類型的表達式,即一個指向 `time.Time` 類型的指針。這實際上是將 `unsafe.Pointer` 轉換後的指針再次轉換回 `*time.Time` 指針類型。 綜上所述,`*((*time.Time)(ptr))` 表達的含義是將 `unsafe.Pointer` 類型的指針 `ptr` 首先轉換為 `*time.Time` 指針類型,然後通過解引用該指針,可以獲取到指向 `time.Time` 類型對象的引用。這種使用 `unsafe.Pointer` 進行類型轉換的操作需要格外小心,因為它繞過了 Go 語言的類型安全性,可能會導致未定義行為或記憶體問題。 * 有兩種作用,一個是定義指針類型,一個是根據內容地址取對應的值,這裡最外面的 * 就是根據記憶體地址取值。 */ func (codec *timeAsInt64Codec) Encode(ptr unsafe.Pointer, stream *jsoniter.Stream) { ts := *((*time.Time)(ptr)) stream.WriteInt64(ts.UnixNano() / codec.precision.Nanoseconds()) }
Encode
函數中,將ptr
轉換成指向time.Time
類型的指針,然後對其解引用拿到了其指向的time.Time
對象。接下來調用其成員函數計算出它對應的unix時間,最後調用stream
的寫入介面將這個int64
的unix時間數值進行編碼輸出,這樣就完成了將原本以對象方式輸出的time.Time
數值,轉換成int64
類型的unix時間輸出IsEmpty
通過同樣方式拿到ptr
指向的time.Time
對象,然後將time.Time
類型"數值為空"定義為其轉換出來的unix時間為0 -
-
ValDecoder
type ValDecoder interface { Decode(ptr unsafe.Pointer, iter *Iterator) }
ValEncoder
實際上是json-iterator內部用於針對某個類型的數據進行反序列化解碼的解碼器,它的成員函數說明如下:-
Decode
Decode
函數用於實現某個類型數據的解碼,ptr
是指向當前待寫入數據的指針,iter
提供不同的介面供使用者將各種類型的數據從輸入源讀入(詳見Iterator章節)。那麼,在這個函數裡面,我們怎麼實現解碼呢?首先,我們調用iter
提供的介面,從json串的輸入源讀入ValDecoder
對應類型的數據,然後將ptr
做一個強轉,將其轉換成指向ValDecoder
對應類型的指針,然後將該指針指向的數據設置成我們通過iter
介面讀取出來的值
還是看
TimeAsInt64Codec
的例子func (codec *timeAsInt64Codec) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) { nanoseconds := iter.ReadInt64() * codec.precision.Nanoseconds() *((*time.Time)(ptr)) = time.Unix(0, nanoseconds) }
Decode
函數中,調用iter
的介面從json輸入源中讀取了一個int64
的數值,接下來因為我們這個ValDecoder
對應的數據類型是time.Time
,這裡把ptr
轉換成指向time.Time
類型的指針,並以我們讀入的int64
數值為unix時間初始化了一個time.Time
對象,最後將它賦給ptr
指向的數值。這樣,我們就完成了從json串中讀入unix時間,並將其轉換成time.Time
對象的功能 -
3.2、定製你的擴展
要定製序列化/反序列化擴展,需要實現Extension
介面,並通過RegisterExtension
進行註冊,Extension
包含以下方法:
type Extension interface {
UpdateStructDescriptor(structDescriptor *StructDescriptor)
CreateMapKeyDecoder(typ reflect2.Type) ValDecoder
CreateMapKeyEncoder(typ reflect2.Type) ValEncoder
CreateDecoder(typ reflect2.Type) ValDecoder
CreateEncoder(typ reflect2.Type) ValEncoder
DecorateDecoder(typ reflect2.Type, decoder ValDecoder) ValDecoder
DecorateEncoder(typ reflect2.Type, encoder ValEncoder) ValEncoder
}
當然,很多情況下,我們只需要用到裡面的部分功能。json-iterator裡面提供了一個DummyExtension
,它是一個最基礎的Extension
實現(基本什麼都不做或返回空)。當你在定義自己的Extension
時,你可以匿名地嵌入DummyExtension
,這樣你就不需要實現所有的Extension
成員,只需要關註自己需要的功能。下麵我們通過一些例子,來說明Extension
的各個成員函數可以用來做什麼
-
UpdateStructDescriptor
UpdateStructDescriptor
函數中,我們可以對結構體的某個欄位定製其編碼/解碼器,或者控制該欄位序列化/反序列化時與哪些字元串綁定type testCodec struct{ } func (codec *testCodec) Encode(ptr unsafe.Pointer, stream *jsoniter.Stream){ str := *((*string)(ptr)) stream.WriteString("TestPrefix_" + str) } func (codec *testCodec) IsEmpty(ptr unsafe.Pointer) bool { str := *((*string)(ptr)) return str == "" } type sampleExtension struct { jsoniter.DummyExtension } func (e *sampleExtension) UpdateStructDescriptor(structDescriptor *jsoniter.StructDescriptor) { // 這個判斷保證我們只針對testStruct結構體,對其他類型無效 if structDescriptor.Type.String() != "main.testStruct" { return } binding := structDescriptor.GetField("TestField") binding.Encoder = &testCodec{} binding.FromNames = []string{"TestField", "Test_Field", "Test-Field"} } func extensionTest(){ type testStruct struct { TestField string } t := testStruct{"fieldValue"} jsoniter.RegisterExtension(&sampleExtension{}) s, _ := jsoniter.MarshalToString(t) fmt.Println(s) // Output: // {"TestField":"TestPrefix_fieldValue"} jsoniter.UnmarshalFromString(`{"Test-Field":"bbb"}`, &t) fmt.Println(t.TestField) // Output: // bbb }
上面的例子,首先我們用
testCodec
實現了一個ValEncoder
,它編碼時在字元串的前面加了一個"TestPrefix_"的首碼再輸出。接著我們註冊了一個sampleExtension
,在UpdateStructDescriptor
函數中我們將testStruct
的TestField
欄位的編碼器設置為我們的testCodec
,最後將其與幾個別名字元串進行了綁定。得到的效果就是,這個結構體序列化輸出時,TestField
的內容會添加上"TestPrefix_"首碼;而反序列化時,TestField
的別名都將映射成這個欄位 -
CreateDecoder
-
CreateEncoder
CreateDecoder
和CreateEncoder
分別用來創建某個數據類型對應的解碼器/編碼器type wrapCodec struct{ encodeFunc func(ptr unsafe.Pointer, stream *jsoniter.Stream) isEmptyFunc func(ptr unsafe.Pointer) bool decodeFunc func(ptr unsafe.Pointer, iter *jsoniter.Iterator) } func (codec *wrapCodec) Encode(ptr unsafe.Pointer, stream *jsoniter.Stream) { codec.encodeFunc(ptr, stream) } func (codec *wrapCodec) IsEmpty(ptr unsafe.Pointer) bool { if codec.isEmptyFunc == nil { return false } return codec.isEmptyFunc(ptr) } func (codec *wrapCodec) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) { codec.decodeFunc(ptr, iter) } type sampleExtension struct { jsoniter.DummyExtension } func (e *sampleExtension) CreateDecoder(typ reflect2.Type) jsoniter.ValDecoder { if typ.Kind() == reflect.Int { return &wrapCodec{ decodeFunc:func(ptr unsafe.Pointer, iter *jsoniter.Iterator) { i := iter.ReadInt() *(*int)(ptr) = i - 1000 }, } } return nil } func (e *sampleExtension) CreateEncoder(typ reflect2.Type) jsoniter.ValEncoder { if typ.Kind() == reflect.Int { return &wrapCodec{ encodeFunc:func(ptr unsafe.Pointer, stream *jsoniter.Stream) { stream.WriteInt(*(*int)(ptr) + 1000) }, isEmptyFunc:nil, } } return nil } func extensionTest(){ i := 20000 jsoniter.RegisterExtension(&sampleExtension{}) s, _ := jsoniter.MarshalToString(i) fmt.Println(s) // Output: // 21000 jsoniter.UnmarshalFromString(`30000`, &i) fmt.Println(i) // Output: // 29000 }
上面的例子我們用
wrapCodec
實現了ValEncoder
和ValDecoder
,然後我們註冊了一個Extension
,這個Extension
的CreateEncoder
函數中設置了wrapCodec
的Encode
函數,指定對於Int
類型的數值+1000後輸出;CreateDecoder
函數中設置了wrapCodec
的Decode
函數,指定讀取了Int
類型的數值後,-1000再進行賦值。這裡要註意的是,不管是CreateEncoder
還是CreateDecoder
函數,我們都通過其typ
參數限定了這個編碼/解碼器只對Int
類型生效 -
CreateMapKeyDecoder
-
CreateMapKeyEncoder
CreateMapKeyDecoder
和CreateMapKeyEncoder
跟上面的CreateDecoder
和CreateEncoder
用法差不多,只不過他們的生效對象是map
類型的key
的,這裡不再舉例詳述了。 -
DecorateDecoder
-
DecorateEncoder
DecorateDecoder
和DecorateEncoder
可以用於裝飾現有的ValEncoder
和ValEncoder
。考慮這麼一個例子,在上述的CreateDecoder
和CreateEncoder
的說明中所舉例的基礎上,我們想再做一層擴展。當我們遇到數字字元串時,我們希望也可以解析成整形數,並且要復用基礎例子中的解碼器,這時候我們就需要用到裝飾器。type decorateExtension struct{ jsoniter.DummyExtension } type decorateCodec struct{ originDecoder jsoniter.ValDecoder } func (codec *decorateCodec) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) { if iter.WhatIsNext() == jsoniter.StringValue { str := iter.ReadString() if _, err := strconv.Atoi(str); err == nil{ newIter := iter.Pool().BorrowIterator([]byte(str)) defer iter.Pool().ReturnIterator(newIter) codec.originDecoder.Decode(ptr, newIter) }else{ codec.originDecoder.Decode(ptr, iter) } } else { codec.originDecoder.Decode(ptr, iter) } } func (e *decorateExtension) DecorateDecoder(typ reflect2.Type, decoder jsoniter.ValDecoder) jsoniter.ValDecoder{ if typ.Kind() == reflect.Int { return &decorateCodec{decoder} } return nil } func extensionTest(){ var i int jsoniter.RegisterExtension(&sampleExtension{}) jsoniter.RegisterExtension(&decorateExtension{}) jsoniter.UnmarshalFromString(`30000`, &i) fmt.Println(i) // Output: // 29000 jsoniter.UnmarshalFromString(`"40000"`, &i) fmt.Println(i) // Output: // 39000 }
在
CreateDecoder
和CreateEncoder
的例子基礎上,我們在註冊一個Extension
,這個Extension
只實現了裝飾器功能,它相容字元串類型數字的解析,並且解析出來的數字依然要-1000再賦值
3.3、作用域
json-iterator有兩個RegisterExtension
介面可以調用,一個是package級別的jsoniter.RegisterExtension
,一個是API
(說明見Config章節)級別的API.RegisterExtension
。這兩個函數都可以用來註冊擴展,但是兩種註冊方式註冊的擴展的作用域略有不同。jsoniter.RegisterExtension
註冊的擴展,對於所有Config
生成的API
都生效;而API.RegisterExtension
只對其對應的Config
生成的API
介面生效,這個需要註意
4、Any(重點)
很多情況下,對於一個json輸入源,我們只對其部分內容感興趣。為了得到我們需要的小部分信息,去定義跟這個json串匹配的schema是一件麻煩的事件。對於體積龐大或者嵌套層次深的json串尤其如此。json-iterator提供了Any
對象,可以很方便地從json串中獲取你想要的元素,而不需要去定義schema
4.1、使用簡單
假設我們有這麼一個json
jsonStr := []byte(`{
"users": [
{
"username": "system",
"avatar_template": "/user_avatar/discourse.metabase.com/system/{size}/6_1.png",
"id": -1
},
{
"username": "zergot",
"avatar_template": "https://avatars.discourse.org/v2/letter/z/0ea827/{size}.png",
"id": 89
}
],
"topics": {
"can_create_topic": false,
"more_topics_url": "/c/uncategorized/l/latest?page=1",
"draft": null,
"draft_key": "new_topic",
"draft_sequence": null,
"per_page": 30,
"topics": [
{
"bumped": true,
"id": 8,
"excerpt": "Welcome to Metabase\u0026#39;s discussion forum. This is a place to get help on installation, setting up as well as sharing tips and tricks.",
"category_id": 1,
"unseen": false,
"slug": "welcome-to-metabases-discussion-forum",
"fancy_title": "Welcome to Metabase\u0026rsquo;s Discussion Forum",
"bookmarked": null,
"archived": false,
"archetype": "regular",
"highest_post_number": 1,
"reply_count": 0,
"visible": true,
"closed": false,
"liked": null,
"posts_count": 1,
"views": 197,
"image_url": "/images/welcome/discourse-edit-post-animated.gif",
"created_at": "2015-10-17T00:14:49.526Z",
"last_posted_at": "2015-10-17T00:14:49.557Z",
"pinned": true,
"title": "Welcome to Metabase's Discussion Forum",
"has_summary": false,
"like_count": 0,
"pinned_globally": true,
"last_poster_username": "system",
"posters": [
{
"extras": "latest single",
"description": "Original Poster, Most Recent Poster", // 我們需要這個
"user_id": -1
}
],
"bumped_at": "2015-10-21T02:32:22.486Z",
"unpinned": null
}
]
}
}`)
如果用傳統的方法,那麼首先我們應該先定義一個匹配這個json結構的結構體,然後調用Unmarshal
來反序列化,再獲取這個結構體中我們需要的欄位的值。如果用Any
,那麼就很簡單了:
any := jsoniter.Get(jsonStr, "topics", "topics", 0, "posters", 0, "description")
fmt.Println(any.ToString())
// Output:
// Original Poster, Most Recent Poster
只需要一行,我們就可以拿到我們想要的元素。然後調用Any
對象提供的介面做下轉換,就得到了我們要的description字元串
4.2、與schema結合
還是上面的例子
jsonStr := []byte(`{
"users": [
{
"username": "system",
"avatar_template": "/user_avatar/discourse.metabase.com/system/{size}/6_1.png",
"id": -1
},
{
"username": "zergot",
"avatar_template": "https://avatars.discourse.org/v2/letter/z/0ea827/{size}.png",
"id": 89
}
],
"topics": {
"can_create_topic": false,
"more_topics_url": "/c/uncategorized/l/latest?page=1",
"draft": null,
"draft_key": "new_topic",
"draft_sequence": null,
"per_page": 30,
"topics": [
{
"bumped": true,
"id": 8,
"excerpt": "Welcome to Metabase\u0026#39;s discussion forum. This is a place to get help on installation, setting up as well as sharing tips and tricks.",
"category_id": 1,
"unseen": false,
"slug": "welcome-to-metabases-discussion-forum",
"fancy_title": "Welcome to Metabase\u0026rsquo;s Discussion Forum",
"bookmarked": null,
"archived": false,
"archetype": "regular",
"highest_post_number": 1,
"reply_count": 0,
"visible": true,
"closed": false,
"liked": null,
"posts_count": 1,
"views": 197,
"image_url": "/images/welcome/discourse-edit-post-animated.gif",
"created_at": "2015-10-17T00:14:49.526Z",
"last_posted_at": "2015-10-17T00:14:49.557Z",
"pinned": true,
"title": "Welcome to Metabase's Discussion Forum",
"has_summary": false,
"like_count": 0,
"pinned_globally": true,
"last_poster_username": "system",
"posters": [
{ // 這次我們需要這個
"extras": "latest single",
"description": "Original Poster, Most Recent Poster",
"user_id": -1
}
],
"bumped_at": "2015-10-21T02:32:22.486Z",
"unpinned": null
}
]
}
}`)
這次我們需要"posters"數組的第一個結構體,我們現在已經有它的schema定義了,除此之外這個json的其他信息我都不需要,那麼如何通過Any
對象獲得這個結構體呢?我們需要ToVal
介面:
type Poster struct {
Extras string `json:"extras"`
Desc string `json:"description"`
UserId int `json:"user_id"`
}
var p Poster
any := jsoniter.Get(jsonStr, "topics", "topics", 0, "posters", 0)
any.ToVal(&p)
fmt.Printf("extras=%s\ndescription=%s\nuser_id=%d\n", p.Extras, p.Desc, p.UserId)
// Output:
// extras=latest single
// description=Original Poster, Most Recent Poster
// user_id=-1
這裡可以看到,首先我們拿到了"posters"第一個元素的Any
對象,然後調用ToVal
方法,就可以像之前的反序列化方法一樣把數據解析出來。實際上,如果你的Any
對象對應的是數組或對象類型的元素,它內部保存了這個元素原始的json串。當你需要獲取其欄位、元素或者將其反序列化出來的時候,才會觸發解析。json-iterator內部將其稱為懶解析。來看個數組的例子:
type User struct {
UserName string `json:"username"`
Template string `json:"avatar_template"`
Id int `json:"id"`
}
var users []User
any := jsoniter.Get(jsonStr, "users")
fmt.Println(any.Get(0, "username").ToString())
// Output:
// system
any.ToVal(&users)
fmt.Printf("username=%s\navatar_template=%s\nid=%d\n", users[1].UserName, users[1].Template, users[1].Id)
// Output:
// username=zergot
// avatar_template=https://avatars.discourse.org/v2/letter/z/0ea827/{size}.png
// id=89
數組元素的獲取方法其實也是類似,這裡不再詳述。
有一點需要說明的是,只有數組和對象的json元素對應的Any
才提供ToVal
方法,也就是說這兩種json元素的Any
對象才實現了懶解析,其他諸如int
,bool
,string
等都沒有實現,實際上它們也不需要什麼懶解析
5、Iterator
json-iterator中使用Iterator
來實現流式解析。通過其提供的API,我們可以控制json串的解析行為,我們可以對json串中與schema定義不一致的欄位做相容性的解析處理,也可以跳過我們不關心的json串中的片段
5.1、創建Iterator實例
有三種方法可以創建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
用於解析的內部緩衝的大小
5.2、定製解析行為
想象一個這樣的場景:我們的數據結構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
5.3、跳過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串。然後我們調用了Iterator
的ReadObjectCB
方法,調用時必須傳入一個回調函數。ReadObjectCB
方法會解析一個對象類型的json串,並迭代這個json串中的頂層對象的每個欄位,對每個欄位都會調用我們一開始傳進去的回調函數。這裡可以看到,在回調函數裡面,我們只是將傳進來的欄位名記錄下來,然後調用Iterator
的Skip
來跳過這個欄位對應的實際內容。Skip
會自動解析json串中接下來的元素是什麼類型的,然後跳過它的解析,跳到下一個欄位。當遍歷完畢後我們就可以拿到我們需要的欄位列表了。
5.4、另一種反序列化介面
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
的緩衝設置得大一點,提高解析效率
5.5、復用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
來使用(至少目前的實現必須這樣)
6、Stream
json-iterator中使用Stream
來控制json的編碼輸出,通過其提供的API,配合自定義的Extension
或ValEncoder
,我們可以定製我們的數據如何編碼輸出成json,甚至可以從頭構造並輸出一個json串
6.1、創建Stream實例
有兩種方法可以創建Stream
實例:
-
從
API
對象的Stream
實例池中Borrow一個c := jsoniter.ConfigDefault s := c.BorrowStream(os.Stdout) defer c.ReturnStream(s) // 你的功能實現 // xxxxxx // ......
使用這種方法"借用"的
Stream
實例,記得在使用完畢後"返還"回去 -
調用
NewStream
介面新建一個s := jsoniter.NewStream(jsoniter.ConfigDefault, os.Stdout, 1024) // 你的功能實現 // xxxxxx // ......
使用這種方法,需要傳入你的序列化配置對應生成的
API
對象,底層輸出的io.Writer
和指定Stream
的內部緩衝內部大小(見下文詳述)
6.2、定製編碼輸出
在定義你的Extension
或ValEncoder
時,你需要用到Stream
來定製你的欄位如何輸出成json
type sampleExtension struct {
jsoniter.DummyExtension
}
type wrapEncoder struct {
encodeFunc func(ptr unsafe.Pointer, stream *jsoniter.Stream)
isEmptyFunc func(ptr unsafe.Pointer) bool
}
func (enc *wrapEncoder) Encode(ptr unsafe.Pointer, stream *jsoniter.Stream) {
enc.encodeFunc(ptr, stream)
}
func (enc *wrapEncoder) IsEmpty(ptr unsafe.Pointer) bool {
if enc.isEmptyFunc == nil {
return false
}
return enc.isEmptyFunc(ptr)
}
func (e *sampleExtension) CreateEncoder(typ reflect2.Type) jsoniter.ValEncoder {
if typ.Kind() == reflect.Int {
return &wrapEncoder{
func(ptr unsafe.Pointer, stream *jsoniter.Stream) {
// 將Int類型的變數的值+1000後,再寫入到輸出的json
stream.WriteInt(*(*int)(ptr) + 1000)
},
nil,
}
}
return nil
}
func streamTest(){
jsoniter.RegisterExtension(&sampleExtension{})
j, _ := jsoniter.MarshalToString(1000)
fmt.Println(j)
// 輸出:2000
}
在上面的例子中,我們註冊了一個Extension
,這個Extension
的CreateEncoder
函數中,我們調用了Stream
的WriteInt
介面,來將ptr
指向的數值加1000後,再輸出成json;在自定義ValEncoder
中,我們同樣使用Stream
提供的函數來定製我們欄位的輸出
type testStructForStream struct{
Field int
}
jsoniter.RegisterFieldEncoderFunc(reflect2.TypeOf(testStructForStream{}).String(), "Field",
func(ptr unsafe.Pointer, stream *jsoniter.Stream) {
// 將Int類型的值轉換成字元串類型的json輸出
stream.WriteString(strconv.Itoa(*(*int)(ptr)))
}, nil)
j, _ := jsoniter.MarshalToString(testStructForStream{1024})
fmt.Println(j)
// 輸出:{"Field":"1024"}
這個例子裡面,我們針對testStructForStream
的Field
欄位註冊了一個ValEncoder
,這個ValEncoder
調用Stream
的WriteString
方法,將ptr
指向的Int
類型數值以字元串的方式寫入到json串
Stream
開放了各種類型數據的寫入方法,可以讓我們很方便地去定製自己的數據以何種方式輸出成json:
WriteBool
WriteInt
WriteFloat32
WriteString
WriteArrayStart
、WriteArrayEnd
WriteObjectStart
、WriteObjectEnd
WriteEmptyArray
WriteEmptyObject
- ......
具體每個方法的說明可以參考godoc
6.3、手動構造json輸出
使用Stream
,可以完全手動地構造你的json如何輸出成位元組流
s := jsoniter.ConfigDefault.BorrowStream(nil)
// 記得把從Config中borrow過來的Stream實例Return回去
defer jsoniter.ConfigDefault.ReturnStream(s)
s.WriteObjectStart()
s.WriteObjectField("EmbedStruct")
s.WriteObjectStart()
s.WriteObjectField("Name")
s.WriteString("xxx")
s.WriteObjectEnd()
s.WriteMore()
s.WriteObjectField("Id")
s.WriteInt(100)
s.WriteObjectEnd()
fmt.Println(string(s.Buffer()))
// 輸出:{"EmbedStruct":{"Name":"xxx"},"Id":100}
不過一般情況下,我們不會也不需要這麼做,更多的時候是創建自己的Extension
或ValEncoder
時調用Stream
的這些方法來定製編碼輸出
6.4、另一種序列化介面
Stream
也提供了一個介面,可以實現跟Encoder
的Encode
方法基本一樣的序列化功能
type testStructForStream struct{
Field int
}
s := jsoniter.NewStream(jsoniter.ConfigDefault, nil, 1024)
s.WriteVal(testStructForStream{300})
result := s.Buffer()
buf := make([]byte, len(result))
copy(buf, result)
fmt.Println(string(buf))
// 輸出:{"Field":300}
在上面這個例子裡面,我們調用NewStream
來創建了一個Stream
實例,然後調用WriteVal
方法來實現序列化,最後將結果位元組序列拷貝出來。通過這種方式,也可以完成序列化。實際上,json-iterator內部也是使用類似的方式,調用Stream
的WriteVal
來完成序列化。這裡有兩點需要說明:
- 調用
NewStream
創建Stream
實例,可以指定Stream
內部緩衝的大小。如果你的使用場景對性能有極致要求,而且序列化輸出的json序列長度可以準確估計的話,不妨使用這個方法來取代直接調用Marshal
來進行序列化。通過指定內部緩衝的初始大小,避免後續在序列化過程中發生的擴容(Stream
內部存儲序列化結果的緩存大小由其指定) - 上述例子中的拷貝操作是必須要進行的,不能直接調用
Stream
的Buffer
方法後返回的切片直接使用。因為Stream會復用其內部已經分配的緩衝,每次序列化都會把之前的內容覆寫掉,因此在下一次調用同一個Stream
實例進行序列化前,你必須把結果拷貝走
6.5、復用Stream實例
如果你需要復用同一個Stream
實例,記得在每次序列化完成後,重置你的Stream
type testStructForStream struct{
Field int
}
s := jsoniter.ConfigDefault.BorrowStream(os.Stdout)
defer jsoniter.ConfigDefault.ReturnStream(s)
s.WriteVal(testStructForStream{300})
result := s.Buffer()
tmp := make([]byte, len(result))
copy(tmp, result)
// xxxxxx
// ......
if s.Error != nil{
return
}
// 記得重置你的Stream
s.Reset(nil)
s.WriteVal(testStructForStream{400})
請註意,如果你的Stream
在序列化過程中出現了錯誤,即Stream.Error
不為nil,那麼你不能繼續使用這個Stream
實例進行新的序列化或編碼輸出,即使你調了Reset
進行重置也不行,只能重新另外創建一個新的Stream
來使用(至少目前的實現必須這樣)
6.6、Flush到輸出設備
如果你在創建Stream
時,指定了使用的io.Writer
,並希望你序列化後的json寫入到這裡,而不是通過調用Buffer
來獲取結果的話,記得在序列化結束的時候調用Flush
來將Stream
中的緩衝刷到你的io.Writer
中
type testStructForStream struct{
Field int
}
s := jsoniter.NewStream(jsoniter.ConfigDefault, os.Stdout, 1024)
s.WriteVal(testStructForStream{300})
// 如果沒有這個調用,你的序列化結果將不會輸出到你指定的Writer
s.Flush()
// 輸出:{"Field":300}
參考教程:
https://zhuanlan.zhihu.com/p/105956945
https://juejin.cn/post/7027306493246439431