github.com/json-iterator/go 詳細教程

来源:https://www.cnblogs.com/huageyiyangdewo/archive/2023/08/27/17660321.html
-Advertisement-
Play Games

最近接觸到了 [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,它指定了testStructTestField欄位名綁定到哪些字元串上,所有綁定的字元串在解析時都當成是該欄位。更多的說明請參考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開啟了EscapeHTMLSortMapKeysValidateJsonRawMessage選項,當你需要近似100%地保證你的序列化/反序列化行為與encoding/json保持一致時,你可以使用這個配置

  • ConfigFastest

    ConfigFastest關閉了EscapeHTML,開啟了MarshalFloatWith6DigitsObjectFieldMustBeSimpleString選項,這個配置可以讓你的序列化/反序列化達到最高效率,但會有某些限制

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的使用之前,需要先介紹一下ValEncoderValDecoder,因為Extension的本質上就是針對不同的類型創建不同的ValEncoderValDecoder實現的。註意,ValEncoder/ValDecoderjson.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函數中我們將testStructTestField欄位的編碼器設置為我們的testCodec,最後將其與幾個別名字元串進行了綁定。得到的效果就是,這個結構體序列化輸出時,TestField的內容會添加上"TestPrefix_"首碼;而反序列化時,TestField的別名都將映射成這個欄位

  • CreateDecoder

  • CreateEncoder

    CreateDecoderCreateEncoder分別用來創建某個數據類型對應的解碼器/編碼器

    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實現了ValEncoderValDecoder,然後我們註冊了一個Extension,這個ExtensionCreateEncoder函數中設置了wrapCodecEncode函數,指定對於Int類型的數值+1000後輸出;CreateDecoder函數中設置了wrapCodecDecode函數,指定讀取了Int類型的數值後,-1000再進行賦值。這裡要註意的是,不管是CreateEncoder還是CreateDecoder函數,我們都通過其typ參數限定了這個編碼/解碼器只對Int類型生效

  • CreateMapKeyDecoder

  • CreateMapKeyEncoder

    CreateMapKeyDecoderCreateMapKeyEncoder跟上面的CreateDecoderCreateEncoder用法差不多,只不過他們的生效對象是map類型的key的,這裡不再舉例詳述了。

  • DecorateDecoder

  • DecorateEncoder

    DecorateDecoderDecorateEncoder可以用於裝飾現有的ValEncoderValEncoder。考慮這麼一個例子,在上述的CreateDecoderCreateEncoder的說明中所舉例的基礎上,我們想再做一層擴展。當我們遇到數字字元串時,我們希望也可以解析成整形數,並且要復用基礎例子中的解碼器,這時候我們就需要用到裝飾器。

    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
    }
    

    CreateDecoderCreateEncoder的例子基礎上,我們在註冊一個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實例:

  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用於解析的內部緩衝的大小

5.2、定製解析行為

想象一個這樣的場景:我們的數據結構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

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串。然後我們調用了IteratorReadObjectCB方法,調用時必須傳入一個回調函數。ReadObjectCB方法會解析一個對象類型的json串,並迭代這個json串中的頂層對象的每個欄位,對每個欄位都會調用我們一開始傳進去的回調函數。這裡可以看到,在回調函數裡面,我們只是將傳進來的欄位名記錄下來,然後調用IteratorSkip來跳過這個欄位對應的實際內容。Skip會自動解析json串中接下來的元素是什麼類型的,然後跳過它的解析,跳到下一個欄位。當遍歷完畢後我們就可以拿到我們需要的欄位列表了。

5.4、另一種反序列化介面

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的緩衝設置得大一點,提高解析效率

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,配合自定義的ExtensionValEncoder,我們可以定製我們的數據如何編碼輸出成json,甚至可以從頭構造並輸出一個json串

6.1、創建Stream實例

有兩種方法可以創建Stream實例:

  1. API對象的Stream實例池中Borrow一個

    c := jsoniter.ConfigDefault
    s := c.BorrowStream(os.Stdout)
    defer c.ReturnStream(s)
    
    // 你的功能實現
    // xxxxxx
    // ......
    

    使用這種方法"借用"的Stream實例,記得在使用完畢後"返還"回去

  2. 調用NewStream介面新建一個

    s := jsoniter.NewStream(jsoniter.ConfigDefault, os.Stdout, 1024)
    
    // 你的功能實現
    // xxxxxx
    // ......
    

    使用這種方法,需要傳入你的序列化配置對應生成的API對象,底層輸出的io.Writer和指定Stream的內部緩衝內部大小(見下文詳述)

6.2、定製編碼輸出

在定義你的ExtensionValEncoder時,你需要用到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,這個ExtensionCreateEncoder函數中,我們調用了StreamWriteInt介面,來將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"}

這個例子裡面,我們針對testStructForStreamField欄位註冊了一個ValEncoder,這個ValEncoder調用StreamWriteString方法,將ptr指向的Int類型數值以字元串的方式寫入到json串

Stream開放了各種類型數據的寫入方法,可以讓我們很方便地去定製自己的數據以何種方式輸出成json:

  • WriteBool
  • WriteInt
  • WriteFloat32
  • WriteString
  • WriteArrayStartWriteArrayEnd
  • WriteObjectStartWriteObjectEnd
  • 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}

不過一般情況下,我們不會也不需要這麼做,更多的時候是創建自己的ExtensionValEncoder時調用Stream的這些方法來定製編碼輸出

6.4、另一種序列化介面

Stream也提供了一個介面,可以實現跟EncoderEncode方法基本一樣的序列化功能

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內部也是使用類似的方式,調用StreamWriteVal來完成序列化。這裡有兩點需要說明:

  • 調用NewStream創建Stream實例,可以指定Stream內部緩衝的大小。如果你的使用場景對性能有極致要求,而且序列化輸出的json序列長度可以準確估計的話,不妨使用這個方法來取代直接調用Marshal來進行序列化。通過指定內部緩衝的初始大小,避免後續在序列化過程中發生的擴容(Stream內部存儲序列化結果的緩存大小由其指定)
  • 上述例子中的拷貝操作是必須要進行的,不能直接調用StreamBuffer方法後返回的切片直接使用。因為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}

參考教程:

Config_cn

https://zhuanlan.zhihu.com/p/105956945

https://juejin.cn/post/7027306493246439431


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • # 背景 再很多場景中,我們可能想在子組件中修改父組件的數據,但事實上,vue不推薦我們這麼做,因為數據的修改不容易溯源。 ## Vue2寫法 在vue2中,我們使用`.sync`修飾符+自定義事件`'update:xxx'`,來使父子組件數據同步。 ```html // 父組件 我是父組件,我有{ ...
  • [系列文章目錄和關於我](https://www.cnblogs.com/cuzzz/p/16609728.html) ## 零丶引入 在[Netty源碼學習2——NioEventLoop的執行](https://www.cnblogs.com/cuzzz/p/17641482.html)中,我們學 ...
  • ## 1.1 註釋 **作用**:在代碼中加一些說明和解釋,方便自己或其他程式員程式員閱讀代碼 **兩種格式** 1. **單行註釋**:`// 描述信息` - 通常放在一行代碼的上方,或者一條語句的末尾,==對該行代碼說明== 2. **多行註釋**: `/* 描述信息 */` - 通常放在一段代 ...
  • ## 1 拉取鏡像 指定版本,在git查看相應版本,參考: https://github.com/openzipkin/zipkin 如2.21.7 ```bash docker pull openzipkin/zipkin:2.21.7 ``` ## 2 啟動 Zipkin預設埠為9411。啟動 ...
  • # Nacos集群搭建 # 1.集群結構圖 官方給出的Nacos集群圖: ![image-20210409210621117](https://img2023.cnblogs.com/blog/3014273/202308/3014273-20230827184442168-301140741.pn ...
  • 最近github上發現了一個庫(`plottable`),可以用簡單的方式就設置出花哨的 `DataFrame` 樣式。 github上的地址:[https://github.com/znstrider/plottable](https://github.com/znstrider/plottabl ...
  • Kafka 是一個基於發佈-訂閱模式的消息系統,它可以在多個生產者和消費者之間傳遞大量的數據。Kafka 的一個顯著特點是它的高吞吐率,即每秒可以處理百萬級別的消息。那麼 Kafka 是如何實現這樣高得性能呢?本文將從七個方面來分析 Kafka 的速度優勢。 - 零拷貝技術 - 僅可追加日誌結構 - ...
  • 在 gRPC 中使用 JWT(JSON Web Tokens)進行身份驗證是一種常見的做法,它可以幫助你確保請求方的身份和許可權。下麵是一種使用 gRPC 和 JWT 進行身份驗證的步驟: 1. **生成和簽發 JWT:** 在用戶登錄成功後,你需要生成一個 JWT 並將其簽發給用戶。JWT 中可以包 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...