github.com/json-iterator/go 详细教程
<p>最近接触到了 github.com/json-iterator/go , 是由滴滴开源的第三方json编码库,它同时提供Go和Java两个版本。</p><blockquote>
<p>文中大量内容来自 github 上的 wiki 文档,有兴趣的朋友可以直接点击 Home 跳转到官方文档查阅。</p>
<p>本文加了些自己的思考以及相关的详细学习例子,废话不多说了,冲!!!</p>
</blockquote>
<h3 id="1基础介绍">1、基础介绍</h3>
<p>json-iterator提供简洁的API,可以让你很方便地进行json序列化/反序列化;与encoding/json完全兼容,使用者可以快速、方便地迁移到json-iterator上来。此外,json-iterator还提供了很多其他方便的功能,如开放的序列化/反序列化配置、Extension、FieldEncoder/FieldDecoder、懒解析Any对象等等增强功能,应对不同使用场景下的json编码和解析,满足各种复杂的需求</p>
<h4 id="11简单的api">1.1、简单的API</h4>
<ul>
<li>
<p><strong>序列化</strong></p>
<pre><code class="language-go">type Student struct{
Name string
Age int
Height float32
}
b, err := jsoniter.Marshal(Student{"Allen", 18, 180.43})
</code></pre>
</li>
<li>
<p><strong>反序列化</strong></p>
<pre><code class="language-go">type Student struct{
Name string
Age int
Height float32
}
var std Student
err := jsoniter.Unmarshal([]byte(`{"Name":"Allen","Age":18,"Height":180.43}`), &std)
</code></pre>
</li>
</ul>
<h4 id="12替代encodingjson">1.2、替代encoding/json</h4>
<p>encoding/json可以很方便地迁移到json-iterator,并且迁移前后代码行为保持一致。不管你是使用基本的Marshal/Unmarshal接口,或是使用Encoder/Decoder,或是你已有的Marshaler/Unmarshaler实现,都能正常地工作。</p>
<blockquote>
<p>这一点还是挺重要的,尤其是对于替换成该 json 库的项目。</p>
</blockquote>
<pre><code class="language-go">// import "encoding/json"
//
// json.Marshal(data)
import "github.com/json-iterator/go"
jsoniter.Marshal(data)
</code></pre>
<p>只需要把import的package替换成"github.com/json-iterator/go",包名从"json",替换成"jsoniter"即可</p>
<h4 id="13序列化反序列化配置">1.3、序列化/反序列化配置</h4>
<p>json-iterator提供了几种序列化/反序列化配置,供不同的场景下的使用</p>
<pre><code class="language-go">api := jsoniter.Config{SortMapKeys:true}.Froze()
b, err := api.Marshal(mapstring{"C":"c", "A":"a", "B":"b"})
</code></pre>
<p>上面的例子中,我们开启了<code>SortMapKeys</code>配置选项,让map序列化输出时字段进行排序输出。更多的选项说明,请参考Config章节</p>
<h4 id="14控制编解码行为">1.4、控制编解码行为</h4>
<p>json-iterator提供了<code>Extension</code>机制,我们可以通过注册自己的<code>Extension</code>,来更精确地控制我们的序列化/反序列化行为</p>
<pre><code class="language-go">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)
</code></pre>
<p>上面的例子中我们注册了一个<code>Extension</code>,它指定了<code>testStruct</code>的<code>TestField</code>字段名绑定到哪些字符串上,所有绑定的字符串在解析时都当成是该字段。更多的说明请参考Extension章节</p>
<h4 id="15快速操作json对象">1.5、快速操作json对象</h4>
<p>json-iterator提供了<code>Any</code>对象,可以让你schemaless地从复杂嵌套json串中提取出感兴趣的部分</p>
<pre><code class="language-go">jsoniter.Get([]byte(`{"Field":{"InnerField":{"Name":"Allen"}}}`), "Field", "InnerField", "Name").ToString()
// output: Allen
</code></pre>
<p>这里<code>Get</code>返回的是一个<code>Any</code>对象,我们获取嵌套结构体最内层的<code>Name</code>字段的<code>Any</code>对象,并调用<code>ToString</code>转换得到我们要的"Allen"字符串。更多的说明请参考Any章节</p>
<h3 id="2config-重点">2、Config (重点)</h3>
<p>下来内容全部来自官网,Config_cn 点击上面链接即可跳转。</p>
<p>json-iterator提供了一些常用的序列化/反序列化选型供配置,使用者可以根据自己需求打开/关闭特定的选项</p>
<h4 id="21配置选项">2.1、配置选项</h4>
<p>要配置序列化/反序列化选项,你需要创建一个<code>Config</code>结构体,并通过设置其字段来设置不同的选项,最后你还需要调用其<code>Froze</code>方法来生成这个<code>Config</code>对应的<code>API</code>对象,通过这个<code>API</code>对象来调用你的配置选项对应的序列化/反序列化函数。</p>
<pre><code class="language-go">api := jsoniter.Config{SortMapKeys:true}.Froze()
api.Marshal(data)
</code></pre>
<p>这里我们创建了一个开启了<code>SortMapKeys</code>选项的<code>Config</code>,并生成其对应的<code>API</code>对象,来进行序列化</p>
<h4 id="22内置配置">2.2、内置配置</h4>
<p>json-iterator提供了三个内置的配置:</p>
<ul>
<li>
<p><strong>ConfigDefault</strong></p>
<p><code>ConfigDefault</code>是默认配置,它开启了<code>EscapeHTML</code>选项。默认配置的意思是说,当你不创建自己的<code>Config</code>对象并生成<code>API</code>对象,而是直接以类似<code>jsoniter.xxx</code>的方式调用接口时,实际上你的序列化/反序列化使用的就是这个<code>ConfigDefault</code>配置</p>
</li>
<li>
<p><strong>ConfigCompatibleWithStandardLibrary</strong></p>
<p><code>ConfigCompatibleWithStandardLibrary</code>开启了<code>EscapeHTML</code>、<code>SortMapKeys</code>和<code>ValidateJsonRawMessage</code>选项,当你需要近似100%地保证你的序列化/反序列化行为与<code>encoding/json</code>保持一致时,你可以使用这个配置</p>
</li>
<li>
<p><strong>ConfigFastest</strong></p>
<p><code>ConfigFastest</code>关闭了<code>EscapeHTML</code>,开启了<code>MarshalFloatWith6Digits</code>和<code>ObjectFieldMustBeSimpleString</code>选项,这个配置可以让你的序列化/反序列化达到最高效率,但会有某些限制</p>
</li>
</ul>
<h4 id="23选项说明">2.3、选项说明</h4>
<ul>
<li>
<p><strong>IndentionStep</strong></p>
<p>指定格式化序列化输出时的空格缩进数量</p>
<pre><code class="language-go">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
// }
}
</code></pre>
</li>
<li>
<p><strong>MarshalFloatWith6Digits</strong></p>
<p>指定浮点数序列化输出时最多保留6位小数</p>
<pre><code class="language-go">c := jsoniter.Config{MarshalFloatWith6Digits:true}.Froze()
if s, err := c.MarshalToString(3.14159265358979); err == nil{
fmt.Println(s)
// Output:
// 3.141593
}
</code></pre>
</li>
<li>
<p><strong>EscapeHTML</strong></p>
<p>开启了这个选项后,如果你的<code>string</code>类型的变量中含有HTML中使用的特殊字符(如'<','>','&'等),序列化时它们会被转义输出</p>
<pre><code class="language-go">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"}
}
</code></pre>
</li>
<li>
<p><strong>SortMapKeys</strong></p>
<p>指定<code>map</code>类型序列化输出时按照其key排序</p>
<pre><code class="language-go">rgb := mapint{
"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":,"green":,"red":,"yellow":}
}
</code></pre>
</li>
<li>
<p><strong>UseNumber</strong></p>
<p>指定反序列化时将数字(整数、浮点数)解析成<code>json.Number</code>类型。</p>
<pre><code class="language-go">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
}
}
</code></pre>
</li>
<li>
<p><strong>DisallowUnknownFields</strong></p>
<p>当开启该选项时,反序列化过程如果解析到未知字段,即在结构体的schema定义中找不到的字段时,不会跳过然后继续解析,而会返回错误</p>
<pre><code class="language-go">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}|...
}
</code></pre>
</li>
<li>
<p><strong>TagKey</strong></p>
<p>指定tag字符串,默认情况为"json",我们可以指定成另一个字符串</p>
<pre><code class="language-go">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}
}
</code></pre>
</li>
<li>
<p><strong>OnlyTaggedField</strong></p>
<p>当开启该选项时,只有带上tag的结构体字段才会被序列化输出</p>
<pre><code class="language-go">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}
}
</code></pre>
</li>
<li>
<p><strong>ValidateJsonRawMessage</strong></p>
<p><code>json.RawMessage</code>类型的字段在序列化时会原封不动地进行输出。开启这个选项后,json-iterator会校验这种类型的字段包含的是否一个合法的json串,如果合法,原样输出;否则会输出"null"</p>
<pre><code class="language-go">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}
}
</code></pre>
</li>
<li>
<p><strong>ObjectFieldMustBeSimpleString</strong></p>
<p>开启该选项后,反序列化过程中不会对你的json串中对象的字段字符串可能包含的转义进行处理,因此你应该保证你的待解析json串中对象的字段应该是简单的字符串(不包含转义)</p>
<pre><code class="language-go">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}
}
</code></pre>
</li>
<li>
<p><strong>CaseSensitive</strong></p>
<p>开启该选项后,你的待解析json串中的对象的字段必须与你的schema定义的字段大小写严格一致</p>
<pre><code class="language-go">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}
}
</code></pre>
</li>
</ul>
<p>我们知道,当我们为<code>为某个类型实现了</code>MarshalJSON()([]byte, error)<code>和</code>UnmarshalJSON(b []byte) error<code>方法,那么这个类型在序列化(MarshalJSON)/反序列化(UnmarshalJSON)时就会使用你定制的相应方法。</code></p>
<p>知道了这个前提知识后,我们来一起看看如何自定义 序列化和反序列化方式。</p>
<h3 id="3extension">3、Extension</h3>
<p><code>Config</code>可以提供部分选项来控制序列化/反序列化的行为,但是不能提供更精细的编码或解析控制,无法应对复杂的需求。json-iterator考虑了这一点,提供了<code>Extension</code>的机制,来满足复杂的序列化/反序列化场景。</p>
<h4 id="31valencodervaldecoder接口">3.1、ValEncoder/ValDecoder接口</h4>
<p>在介绍<code>Extension</code>的使用之前,需要先介绍一下<code>ValEncoder</code>和<code>ValDecoder</code>,因为<code>Extension</code>的本质上就是针对不同的类型创建不同的<code>ValEncoder</code>和<code>ValDecoder</code>实现的。注意,<code>ValEncoder</code>/<code>ValDecoder</code>和<code>json.Encoder</code>/<code>json.Decoder</code>是不一样的概念,不要混淆了。</p>
<ul>
<li>
<p><strong>ValEncoder</strong></p>
<pre><code class="language-go">type ValEncoder interface {
IsEmpty(ptr unsafe.Pointer) bool
Encode(ptr unsafe.Pointer, stream *Stream)
}
</code></pre>
<p><code>ValEncoder</code>实际上是json-iterator内部用于针对某个类型的数据进行序列化编码的编码器,它的两个成员函数说明如下:</p>
<ul>
<li>
<p>Encode</p>
<p><code>Encode</code>函数用于实现某个类型数据的编码,<code>ptr</code>是指向当前待编码数据的指针,<code>stream</code>提供不同的接口供使用者将各种类型的数据写入到输出设备(详见Stream章节)。那么,在这个函数里面,我们怎么实现编码呢?实际上,我们大部分时间做的,就是将<code>ptr</code>转换成这个<code>ValEncoder</code>对应的数据类型的指针,然后调用<code>stream</code>的接口,将<code>ptr</code>指向的数值进行编码输出</p>
</li>
<li>
<p>IsEmpty</p>
<p><code>IsEmpty</code>是跟<code>omitempty</code>这个tag相关的函数。我们都知道,在一个结构体里面,如果某个字段的tag带上了<code>omitempty</code>属性,那么当这个字段对应的"数值为空"时,这个字段在序列化时不会被编码输出。那么什么叫"数值为空"呢?对于不同类型的数据,恐怕应该是有不同的定义的。因此<code>IsEmpty</code>这个函数里面,就是需要你去实现,你的<code>ValEncoder</code>对应的数据类型在实际数值是什么的时候,称作"数值为空"</p>
</li>
</ul>
<p>我们看一个具体的例子,来帮助我们理解<code>ValEncoder</code>。json-iterator提供了一个内置的<code>TimeAsInt64Codec</code>,来看看它的实现:</p>
<pre><code class="language-go">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())
}
</code></pre>
<p><code>Encode</code>函数中,将<code>ptr</code>转换成指向<code>time.Time</code>类型的指针,然后对其解引用拿到了其指向的<code>time.Time</code>对象。接下来调用其成员函数计算出它对应的unix时间,最后调用<code>stream</code>的写入接口将这个<code>int64</code>的unix时间数值进行编码输出,这样就完成了将原本以对象方式输出的<code>time.Time</code>数值,转换成<code>int64</code>类型的unix时间输出</p>
<p><code>IsEmpty</code>通过同样方式拿到<code>ptr</code>指向的<code>time.Time</code>对象,然后将<code>time.Time</code>类型"数值为空"定义为其转换出来的unix时间为0</p>
</li>
<li>
<p><strong>ValDecoder</strong></p>
<pre><code class="language-go">type ValDecoder interface {
Decode(ptr unsafe.Pointer, iter *Iterator)
}
</code></pre>
<p><code>ValEncoder</code>实际上是json-iterator内部用于针对某个类型的数据进行反序列化解码的解码器,它的成员函数说明如下:</p>
<ul>
<li>
<p>Decode</p>
<p><code>Decode</code>函数用于实现某个类型数据的解码,<code>ptr</code>是指向当前待写入数据的指针,<code>iter</code>提供不同的接口供使用者将各种类型的数据从输入源读入(详见Iterator章节)。那么,在这个函数里面,我们怎么实现解码呢?首先,我们调用<code>iter</code>提供的接口,从json串的输入源读入<code>ValDecoder</code>对应类型的数据,然后将<code>ptr</code>做一个强转,将其转换成指向<code>ValDecoder</code>对应类型的指针,然后将该指针指向的数据设置成我们通过<code>iter</code>接口读取出来的值</p>
</li>
</ul>
<p>还是看<code>TimeAsInt64Codec</code>的例子</p>
<pre><code class="language-go">func (codec *timeAsInt64Codec) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) {
nanoseconds := iter.ReadInt64() * codec.precision.Nanoseconds()
*((*time.Time)(ptr)) = time.Unix(0, nanoseconds)
}
</code></pre>
<p><code>Decode</code>函数中,调用<code>iter</code>的接口从json输入源中读取了一个<code>int64</code>的数值,接下来因为我们这个<code>ValDecoder</code>对应的数据类型是<code>time.Time</code>,这里把<code>ptr</code>转换成指向<code>time.Time</code>类型的指针,并以我们读入的<code>int64</code>数值为unix时间初始化了一个<code>time.Time</code>对象,最后将它赋给<code>ptr</code>指向的数值。这样,我们就完成了从json串中读入unix时间,并将其转换成<code>time.Time</code>对象的功能</p>
</li>
</ul>
<h4 id="32定制你的扩展">3.2、定制你的扩展</h4>
<p>要定制序列化/反序列化扩展,需要实现<code>Extension</code>接口,并通过<code>RegisterExtension</code>进行注册,<code>Extension</code>包含以下方法:</p>
<pre><code class="language-go">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
}
</code></pre>
<p>当然,很多情况下,我们只需要用到里面的部分功能。json-iterator里面提供了一个<code>DummyExtension</code>,它是一个最基础的<code>Extension</code>实现(基本什么都不做或返回空)。当你在定义自己的<code>Extension</code>时,你可以匿名地嵌入<code>DummyExtension</code>,这样你就不需要实现所有的<code>Extension</code>成员,只需要关注自己需要的功能。下面我们通过一些例子,来说明<code>Extension</code>的各个成员函数可以用来做什么</p>
<ul>
<li>
<p><strong>UpdateStructDescriptor</strong></p>
<p><code>UpdateStructDescriptor</code>函数中,我们可以对结构体的某个字段定制其编码/解码器,或者控制该字段序列化/反序列化时与哪些字符串绑定</p>
<pre><code class="language-go">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
}
</code></pre>
<p>上面的例子,首先我们用<code>testCodec</code>实现了一个<code>ValEncoder</code>,它编码时在字符串的前面加了一个"TestPrefix_"的前缀再输出。接着我们注册了一个<code>sampleExtension</code>,在<code>UpdateStructDescriptor</code>函数中我们将<code>testStruct</code>的<code>TestField</code>字段的编码器设置为我们的<code>testCodec</code>,最后将其与几个别名字符串进行了绑定。得到的效果就是,这个结构体序列化输出时,<code>TestField</code>的内容会添加上"TestPrefix_"前缀;而反序列化时,<code>TestField</code>的别名都将映射成这个字段</p>
</li>
<li>
<p><strong>CreateDecoder</strong></p>
</li>
<li>
<p><strong>CreateEncoder</strong></p>
<p><code>CreateDecoder</code>和<code>CreateEncoder</code>分别用来创建某个数据类型对应的解码器/编码器</p>
<pre><code class="language-go">type wrapCodec struct{
encodeFuncfunc(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
}
</code></pre>
<p>上面的例子我们用<code>wrapCodec</code>实现了<code>ValEncoder</code>和<code>ValDecoder</code>,然后我们注册了一个<code>Extension</code>,这个<code>Extension</code>的<code>CreateEncoder</code>函数中设置了<code>wrapCodec</code>的<code>Encode</code>函数,指定对于<code>Int</code>类型的数值+1000后输出;<code>CreateDecoder</code>函数中设置了<code>wrapCodec</code>的<code>Decode</code>函数,指定读取了<code>Int</code>类型的数值后,-1000再进行赋值。这里要注意的是,不管是<code>CreateEncoder</code>还是<code>CreateDecoder</code>函数,我们都通过其<code>typ</code>参数限定了这个编码/解码器只对<code>Int</code>类型生效</p>
</li>
<li>
<p><strong>CreateMapKeyDecoder</strong></p>
</li>
<li>
<p><strong>CreateMapKeyEncoder</strong></p>
<p><code>CreateMapKeyDecoder</code>和<code>CreateMapKeyEncoder</code>跟上面的<code>CreateDecoder</code>和<code>CreateEncoder</code>用法差不多,只不过他们的生效对象是<code>map</code>类型的<code>key</code>的,这里不再举例详述了。</p>
</li>
<li>
<p><strong>DecorateDecoder</strong></p>
</li>
<li>
<p><strong>DecorateEncoder</strong></p>
<p><code>DecorateDecoder</code>和<code>DecorateEncoder</code>可以用于装饰现有的<code>ValEncoder</code>和<code>ValEncoder</code>。考虑这么一个例子,在上述的<code>CreateDecoder</code>和<code>CreateEncoder</code>的说明中所举例的基础上,我们想再做一层扩展。当我们遇到数字字符串时,我们希望也可以解析成整形数,并且要复用基础例子中的解码器,这时候我们就需要用到装饰器。</p>
<pre><code class="language-go">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
}
</code></pre>
<p>在<code>CreateDecoder</code>和<code>CreateEncoder</code>的例子基础上,我们在注册一个<code>Extension</code>,这个<code>Extension</code>只实现了装饰器功能,它兼容字符串类型数字的解析,并且解析出来的数字依然要-1000再赋值</p>
</li>
</ul>
<h4 id="33作用域">3.3、作用域</h4>
<p>json-iterator有两个<code>RegisterExtension</code>接口可以调用,一个是package级别的<code>jsoniter.RegisterExtension</code>,一个是<code>API</code>(说明见Config章节)级别的<code>API.RegisterExtension</code>。这两个函数都可以用来注册扩展,但是两种注册方式注册的扩展的作用域略有不同。<code>jsoniter.RegisterExtension</code>注册的扩展,对于所有<code>Config</code>生成的<code>API</code>都生效;而<code>API.RegisterExtension</code>只对其对应的<code>Config</code>生成的<code>API</code>接口生效,这个需要注意</p>
<h3 id="4any重点">4、Any(重点)</h3>
<p>很多情况下,对于一个json输入源,我们只对其部分内容感兴趣。为了得到我们需要的小部分信息,去定义跟这个json串匹配的schema是一件麻烦的事件。对于体积庞大或者嵌套层次深的json串尤其如此。json-iterator提供了<code>Any</code>对象,可以很方便地从json串中获取你想要的元素,而不需要去定义schema</p>
<h4 id="41使用简单">4.1、使用简单</h4>
<p>假设我们有这么一个json</p>
<pre><code class="language-go">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
}
]
}
}`)
</code></pre>
<p>如果用传统的方法,那么首先我们应该先定义一个匹配这个json结构的结构体,然后调用<code>Unmarshal</code>来反序列化,再获取这个结构体中我们需要的字段的值。如果用<code>Any</code>,那么就很简单了:</p>
<pre><code class="language-go">any := jsoniter.Get(jsonStr, "topics", "topics", 0, "posters", 0, "description")
fmt.Println(any.ToString())
// Output:
// Original Poster, Most Recent Poster
</code></pre>
<p>只需要一行,我们就可以拿到我们想要的元素。然后调用<code>Any</code>对象提供的接口做下转换,就得到了我们要的description字符串</p>
<h4 id="42与schema结合">4.2、与schema结合</h4>
<p>还是上面的例子</p>
<pre><code class="language-go">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
}
]
}
}`)
</code></pre>
<p>这次我们需要"posters"数组的第一个结构体,我们现在已经有它的schema定义了,除此之外这个json的其他信息我都不需要,那么如何通过<code>Any</code>对象获得这个结构体呢?我们需要<code>ToVal</code>接口:</p>
<pre><code class="language-go">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
</code></pre>
<p>这里可以看到,首先我们拿到了"posters"第一个元素的<code>Any</code>对象,然后调用<code>ToVal</code>方法,就可以像之前的反序列化方法一样把数据解析出来。实际上,如果你的<code>Any</code>对象对应的是数组或对象类型的元素,它内部保存了这个元素原始的json串。当你需要获取其字段、元素或者将其反序列化出来的时候,才会触发解析。json-iterator内部将其称为懒解析。来看个数组的例子:</p>
<pre><code class="language-go">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.UserName, users.Template, users.Id)
// Output:
// username=zergot
// avatar_template=https://avatars.discourse.org/v2/letter/z/0ea827/{size}.png
// id=89
</code></pre>
<p>数组元素的获取方法其实也是类似,这里不再详述。</p>
<p><strong>有一点需要说明的是,只有数组和对象的json元素对应的<code>Any</code>才提供<code>ToVal</code>方法,也就是说这两种json元素的<code>Any</code>对象才实现了懒解析,其他诸如<code>int</code>,<code>bool</code>,<code>string</code>等都没有实现,实际上它们也不需要什么懒解析</strong></p>
<h3 id="5iterator">5、Iterator</h3>
<p>json-iterator中使用<code>Iterator</code>来实现流式解析。通过其提供的API,我们可以控制json串的解析行为,我们可以对json串中与schema定义不一致的字段做兼容性的解析处理,也可以跳过我们不关心的json串中的片段</p>
<h4 id="51创建iterator实例">5.1、创建Iterator实例</h4>
<p>有三种方法可以创建<code>Iterator</code>实例:</p>
<ol>
<li>
<p>从<code>API</code>对象的<code>Iterator</code>实例池中Borrow一个</p>
<pre><code class="language-go">c := jsoniter.ConfigDefault
i := c.BorrowIterator([]byte(`{"A":"a"}`))
defer c.ReturnIterator(i)
// 你的功能实现
// xxxxxx
// ......
</code></pre>
<p>使用这种方法"借用"的<code>Iterator</code>实例,记得在使用完毕后"返还"回去</p>
</li>
<li>
<p>调用<code>NewIterator</code>接口新建一个</p>
<pre><code class="language-go">i := jsoniter.NewIterator(jsoniter.ConfigDefault)
i.Reset(os.Stdin)
// 或者i.ResetBytes(`{"A":"a"}`)
// 你的功能实现
// xxxxxx
// ......
</code></pre>
<p>使用这种方法,需要传入你的序列化配置对应生成的<code>API</code>对象。对于这种方法,要指定输入源<code>io.Reader</code>或输入json串都只能在创建了<code>Iterator</code>后,调用其重置方法<code>Reset</code>或<code>ResetBytes</code>来设置其待解析输入。如果要在创建的时候就指定输入源,可以用第三种方法</p>
</li>
<li>
<p>调用<code>ParseXXX</code>方法新建一个</p>
<pre><code class="language-go">i := jsoniter.Parse(jsoniter.ConfigDefault, os.Stdin, 1024)
// 或者 i := jsoniter.ParseBytes(jsoniter.ConfigDefault, []byte(`{"A":"a"}`))
// 或者 i := jsoniter.ParseString(jsoniter.ConfigDefault, `{"A":"a"}`)
// 你的功能实现
// xxxxxx
// ......
</code></pre>
<p>使用<code>Parse</code>族的方法,可以在创建<code>Iterator</code>的时候指定待解析json串的输入源。其中<code>Parse</code>方法还可以指定<code>Iterator</code>用于解析的内部缓冲的大小</p>
</li>
</ol>
<h4 id="52定制解析行为">5.2、定制解析行为</h4>
<p>想象一个这样的场景:我们的数据结构schema中某个字段定义成了<code>bool</code>类型,但是我们接收到的json串中,该字段对应的值可能是<code>bool</code>类型,可能是<code>int</code>类型,还可能是<code>string</code>类型,我们需要对其做兼容性的解析处理,这时候<code>Iterator</code>(配合<code>Extension</code>或<code>ValDecoder</code>)就可以发挥作用了。</p>
<pre><code class="language-go">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
}
</code></pre>
<p>在上面这个例子里面,我们针对<code>testStructForIterator</code>的<code>BoolField</code>字段注册了一个<code>ValDecoder</code>。在它的<code>Decode</code>方法中,我们先调用<code>Iterator</code>的<code>WhatIsNext</code>方法,通过json串中下一个元素的类似,来决定调用<code>Iterator</code>的哪个方法来解析下一个数值,根据解析结果,设置<code>ptr</code>指向的<code>bool</code>类型的数据值。这样不管我们解析的json串中,<code>BoolField</code>字段实际使用布尔、数值或是字符串来表示,我们都可以做到兼容</p>
<p><code>Iterator</code>开放了各种接口用于从输入中读入不同类型的数据:</p>
<ul>
<li><code>ReadBool</code></li>
<li><code>ReadString</code></li>
<li><code>ReadInt</code></li>
<li><code>ReadFloat32</code></li>
<li><code>ReadMapCB</code></li>
<li><code>ReadObjectCB</code></li>
<li><code>ReadArrayCB</code></li>
<li>......</li>
</ul>
<p>具体每个方法的说明可以参考godoc</p>
<h4 id="53跳过json片段">5.3、跳过json片段</h4>
<p>使用<code>Iterator</code>,我们可以跳过json串中的特定片段,只处理我们感兴趣的部分。考虑这么一个场景:我们接收到一个json串,这个json串中包含了一个对象,我们只想把这个对象的每个字段的字段名记录下来,至于字段对应的具体内容,我们不关心。为了实现这样的需求,我们需要用到<code>Iterator</code></p>
<pre><code class="language-go">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)
// 输出:
</code></pre>
<p>在上面的例子中,我们调用了<code>ParseString</code>来创建一个<code>Iterator</code>实例。<code>ParseString</code>可以指定<code>Iterator</code>实例对应的配置和作为解析源的json串。然后我们调用了<code>Iterator</code>的<code>ReadObjectCB</code>方法,调用时必须传入一个回调函数。<code>ReadObjectCB</code>方法会解析一个对象类型的json串,并迭代这个json串中的顶层对象的每个字段,对每个字段都会调用我们一开始传进去的回调函数。这里可以看到,在回调函数里面,我们只是将传进来的字段名记录下来,然后调用<code>Iterator</code>的<code>Skip</code>来跳过这个字段对应的实际内容。<code>Skip</code>会自动解析json串中接下来的元素是什么类型的,然后跳过它的解析,跳到下一个字段。当遍历完毕后我们就可以拿到我们需要的字段列表了。</p>
<h4 id="54另一种反序列化接口">5.4、另一种反序列化接口</h4>
<p><code>Iterator</code>也提供了一个接口,可以实现跟<code>Decoder</code>的<code>Decode</code>方法基本一样的序列化功能</p>
<pre><code class="language-go">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}
}
</code></pre>
<p>在上面这个例子里面,我们调用<code>Parse</code>来创建了一个<code>Iterator</code>实例,不设置输入设备<code>io.Reader</code>,我们用<code>ResetBytes</code>来设置待解析的json串,然后调用<code>ReadVal</code>方法来实现序列化。通过这种方式,也可以完成反序列化。实际上,json-iterator内部也是使用类似的方式,调用<code>Iterator</code>的<code>ReadVal</code>来完成反序列化。这里有一点需要说明:</p>
<ul>
<li>调用<code>Parse</code>创建<code>Iterator</code>实例,可以指定<code>Iterator</code>内部缓冲的大小。对于解析输入源从<code>io.Reader</code>读入的应用场合,由于<code>Iterator</code>的内部流式实现,是不会一次过将数据从<code>io.Reader</code>全部读取出来然后解析的,而是每次读入不超过缓冲区长度的大小的数据,然后解析。当解析过程发现缓冲区中数据已经解析完,又会从<code>io.Reader</code>中读取数据到缓冲区,继续解析,直至整个完整的json串解析完毕。考虑这么一个例子:你的<code>Iterator</code>的缓冲区大小设置为1024,但你的<code>io.Reader</code>里面有10M的json串需要解析,这样大概可以认为要把这个json串解析完,需要从<code>io.Reader</code>读入数据10240次,每次读1024字节。因此,如果你的解析源需要从<code>io.Reader</code>中读入,对性能要求较高,而对内存占用不太敏感,那么不妨放弃直接调用<code>Unmarshal</code>,自己创建<code>Iterator</code>来进行反序列化,并适当将<code>Iterator</code>的缓冲设置得大一点,提高解析效率</li>
</ul>
<h4 id="55复用iterator实例">5.5、复用Iterator实例</h4>
<p>你可以调用<code>Reset</code>(解析源为<code>io.Reader</code>)或者<code>ResetBytes</code>(解析源为字符串或字节序列)来复用你的<code>Iterator</code>实例</p>
<pre><code class="language-go">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)
</code></pre>
<p><strong>请注意,如果你的<code>Iterator</code>在反序列化过程中出现了错误,即<code>Iterator.Error</code>不为nil,那么你不能继续使用这个<code>Iterator</code>实例进行新的反序列化或解码,即使你调了<code>Reset</code>/<code>ResetBytes</code>进行重置也不行,只能重新另外创建一个新的<code>Iterator</code>来使用(至少目前的实现必须这样)</strong></p>
<h3 id="6stream">6、Stream</h3>
<p>json-iterator中使用<code>Stream</code>来控制json的编码输出,通过其提供的API,配合自定义的<code>Extension</code>或<code>ValEncoder</code>,我们可以定制我们的数据如何编码输出成json,甚至可以从头构造并输出一个json串</p>
<h4 id="61创建stream实例">6.1、创建Stream实例</h4>
<p>有两种方法可以创建<code>Stream</code>实例:</p>
<ol>
<li>
<p>从<code>API</code>对象的<code>Stream</code>实例池中Borrow一个</p>
<pre><code class="language-go">c := jsoniter.ConfigDefault
s := c.BorrowStream(os.Stdout)
defer c.ReturnStream(s)
// 你的功能实现
// xxxxxx
// ......
</code></pre>
<p>使用这种方法"借用"的<code>Stream</code>实例,记得在使用完毕后"返还"回去</p>
</li>
<li>
<p>调用<code>NewStream</code>接口新建一个</p>
<pre><code class="language-go">s := jsoniter.NewStream(jsoniter.ConfigDefault, os.Stdout, 1024)
// 你的功能实现
// xxxxxx
// ......
</code></pre>
<p>使用这种方法,需要传入你的序列化配置对应生成的<code>API</code>对象,底层输出的<code>io.Writer</code>和指定<code>Stream</code>的内部缓冲内部大小(见下文详述)</p>
</li>
</ol>
<h4 id="62定制编码输出">6.2、定制编码输出</h4>
<p>在定义你的<code>Extension</code>或<code>ValEncoder</code>时,你需要用到<code>Stream</code>来定制你的字段如何输出成json</p>
<pre><code class="language-go">type sampleExtension struct {
jsoniter.DummyExtension
}
type wrapEncoder struct {
encodeFuncfunc(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
}
</code></pre>
<p>在上面的例子中,我们注册了一个<code>Extension</code>,这个<code>Extension</code>的<code>CreateEncoder</code>函数中,我们调用了<code>Stream</code>的<code>WriteInt</code>接口,来将<code>ptr</code>指向的数值加1000后,再输出成json;在自定义<code>ValEncoder</code>中,我们同样使用<code>Stream</code>提供的函数来定制我们字段的输出</p>
<pre><code class="language-go">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"}
</code></pre>
<p>这个例子里面,我们针对<code>testStructForStream</code>的<code>Field</code>字段注册了一个<code>ValEncoder</code>,这个<code>ValEncoder</code>调用<code>Stream</code>的<code>WriteString</code>方法,将<code>ptr</code>指向的<code>Int</code>类型数值以字符串的方式写入到json串</p>
<p><code>Stream</code>开放了各种类型数据的写入方法,可以让我们很方便地去定制自己的数据以何种方式输出成json:</p>
<ul>
<li><code>WriteBool</code></li>
<li><code>WriteInt</code></li>
<li><code>WriteFloat32</code></li>
<li><code>WriteString</code></li>
<li><code>WriteArrayStart</code>、<code>WriteArrayEnd</code></li>
<li><code>WriteObjectStart</code>、<code>WriteObjectEnd</code></li>
<li><code>WriteEmptyArray</code></li>
<li><code>WriteEmptyObject</code></li>
<li>......</li>
</ul>
<p>具体每个方法的说明可以参考godoc</p>
<h4 id="63手动构造json输出">6.3、手动构造json输出</h4>
<p>使用<code>Stream</code>,可以完全手动地构造你的json如何输出成字节流</p>
<pre><code class="language-go">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}
</code></pre>
<p>不过一般情况下,我们不会也不需要这么做,更多的时候是创建自己的<code>Extension</code>或<code>ValEncoder</code>时调用<code>Stream</code>的这些方法来定制编码输出</p>
<h4 id="64另一种序列化接口">6.4、另一种序列化接口</h4>
<p><code>Stream</code>也提供了一个接口,可以实现跟<code>Encoder</code>的<code>Encode</code>方法基本一样的序列化功能</p>
<pre><code class="language-go">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}
</code></pre>
<p>在上面这个例子里面,我们调用<code>NewStream</code>来创建了一个<code>Stream</code>实例,然后调用<code>WriteVal</code>方法来实现序列化,最后将结果字节序列拷贝出来。通过这种方式,也可以完成序列化。实际上,json-iterator内部也是使用类似的方式,调用<code>Stream</code>的<code>WriteVal</code>来完成序列化。这里有两点需要说明:</p>
<ul>
<li>调用<code>NewStream</code>创建<code>Stream</code>实例,可以指定<code>Stream</code>内部缓冲的大小。如果你的使用场景对性能有极致要求,而且序列化输出的json序列长度可以准确估计的话,不妨使用这个方法来取代直接调用<code>Marshal</code>来进行序列化。通过指定内部缓冲的初始大小,避免后续在序列化过程中发生的扩容(<code>Stream</code>内部存储序列化结果的缓存大小由其指定)</li>
<li>上述例子中的拷贝操作是必须要进行的,不能直接调用<code>Stream</code>的<code>Buffer</code>方法后返回的切片直接使用。因为Stream会复用其内部已经分配的缓冲,每次序列化都会把之前的内容复写掉,因此在下一次调用同一个<code>Stream</code>实例进行序列化前,你必须把结果拷贝走</li>
</ul>
<h4 id="65复用stream实例">6.5、复用Stream实例</h4>
<p>如果你需要复用同一个<code>Stream</code>实例,记得在每次序列化完成后,重置你的<code>Stream</code></p>
<pre><code class="language-go">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})
</code></pre>
<p><strong>请注意,如果你的<code>Stream</code>在序列化过程中出现了错误,即<code>Stream.Error</code>不为nil,那么你不能继续使用这个<code>Stream</code>实例进行新的序列化或编码输出,即使你调了<code>Reset</code>进行重置也不行,只能重新另外创建一个新的<code>Stream</code>来使用(至少目前的实现必须这样)</strong></p>
<h4 id="66flush到输出设备">6.6、Flush到输出设备</h4>
<p>如果你在创建<code>Stream</code>时,指定了使用的<code>io.Writer</code>,并希望你序列化后的json写入到这里,而不是通过调用<code>Buffer</code>来获取结果的话,记得在序列化结束的时候调用<code>Flush</code>来将<code>Stream</code>中的缓冲刷到你的<code>io.Writer</code>中</p>
<pre><code class="language-go">type testStructForStream struct{
Field int
}
s := jsoniter.NewStream(jsoniter.ConfigDefault, os.Stdout, 1024)
s.WriteVal(testStructForStream{300})
// 如果没有这个调用,你的序列化结果将不会输出到你指定的Writer
s.Flush()
// 输出:{"Field":300}
</code></pre>
<p>参考教程:</p>
<p>Config_cn</p>
<p>https://zhuanlan.zhihu.com/p/105956945</p>
<p>https://juejin.cn/post/7027306493246439431</p><br><br>
来源:https://www.cnblogs.com/huageyiyangdewo/p/17660321.html
頁:
[1]