<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Go 填坑:使用 UnmarshalJSON 接口實現自定義 unmarshal 的坑

          共 4924字,需瀏覽 10分鐘

           ·

          2020-08-26 13:09

          golang 使用 UnmarshalJSON 實現自定義 marshal/unmarshal 的坑

          背景

          Go 語言標準庫?encoding/json?提供了操作 JSON 的方法,一般可以使用?json.Marshal?和?json.Unmarshal?來序列化和解析 JSON 字符串。當你想實現自定義的 Unmarshal 方法,就要實現 Unmarshaler 接口。一位老哥在 golang/go 項目下提了一個類似的 issue:https://github.com/golang/go/issues/39470 , 無意間點進去發(fā)現這個問題還挺有意思的,自己經過實踐后才發(fā)現,這應該是 golang 中的一個大坑。

          先來看一下這位仁兄遇到了什么問題:

          ?package?main

          import?(
          ?"encoding/json"
          ?"fmt"
          ?"time"
          )

          var?testJSON?=?`{"num":5,"duration":"5s"}`

          type?Nested?struct?{
          ?Dur?time.Duration?`json:"duration"`
          }

          func?(n?*Nested)?UnmarshalJSON(data?[]byte)?error?{
          ?*n?=?Nested{}
          ?tmp?:=?struct?{
          ??Dur?string?`json:"duration"`
          ?}{}
          ?fmt.Printf("parsing?nested?json?%s?\n",?string(data))
          ?if?err?:=?json.Unmarshal(data,?&tmp);?err?!=?nil?{
          ??fmt.Printf("failed?to?parse?nested:?%v",?err)
          ??return?err
          ?}
          ?tmpDur,?err?:=?time.ParseDuration(tmp.Dur)
          ?if?err?!=?nil?{
          ??fmt.Printf("failed?to?parse?duration:?%v",?err)
          ??return?err
          ?}
          ?(*n).Dur?=?tmpDur
          ?return?nil
          }

          type?Object?struct?{
          ?Nested
          ?Num?int?`json:"num"`
          }

          //uncommenting?this?method?still?doesnt?help.
          //tmp?is?parsed?with?the?completed?json?at?Nested
          //which?doesnt?take?care?of?Num?field,?so?Num?is?zero?value.
          func?(o?*Object)?UnmarshalJSON(data?[]byte)?error?{
          ?*o?=?Object{}
          ?tmp?:=?struct?{
          ??Nested
          ??Num?int?`json:"num"`
          ?}{}
          ?fmt.Printf("parsing?object?json?%s?\n",?string(data))
          ?if?err?:=?json.Unmarshal(data,?&tmp);?err?!=?nil?{
          ??fmt.Printf("failed?to?parse?object:?%v",?err)
          ??return?err
          ?}
          ?fmt.Printf("tmp?object:?%+v?\n",?tmp)
          ?(*o).Num?=?tmp.Num
          ?(*o).Nested?=?tmp.Nested
          ?return?nil
          }

          func?main()?{
          ?obj?:=?Object{}
          ?if?err?:=?json.Unmarshal([]byte(testJSON),?&obj);?err?!=?nil?{
          ??fmt.Printf("failed?to?parse?result:?%v",?err)
          ??return
          ?}
          ?fmt.Printf("result:?%+v?\n",?obj)
          }

          代碼看起來是要實現一個帶有自定義功能的 unmarshal ,Object 結構體內嵌了 Nested 結構體,并且?guī)в幸粋€ Num 字段,想要把 json string {"num":5,"duration":"5s"} unmarshal 到結構體 Object 中。代碼看上去沒什么問題,Object ?中嵌入了 Nested,都實現了 UnmarshalJSON, 符合了 json 包中 Unmarshaler 接口。

          package?json
          ..........
          /?By?convention,?to?approximate?the?behavior?of?Unmarshal?itself,
          //?Unmarshalers?implement?UnmarshalJSON([]byte("null"))?as?a?no-op.
          type?Unmarshaler?interface?{
          ?UnmarshalJSON([]byte)?error
          }

          當一切準備就緒的時候,讓我們執(zhí)行代碼。

          現象是,Num 字段并沒有被解析成功 ? 。

          分析問題

          代碼看起來并沒有什么問題,用回歸本質的方式解釋起來就是,結構體嵌入并實現接口方法。那先讓我們來看一段回歸本質的代碼:

          package?main

          import?"fmt"

          type?Funer?interface{
          ????Name()string
          ????PrintName()
          }

          type?A?struct?{
          }

          func?(a?*A)?Name()?string?{
          ????return?"a"
          }

          func?(a?*A)?PrintName()?{
          ????fmt.Println(a.Name())
          }

          type?B?struct?{
          ????A
          }

          func?(b?*B)?Name()?string?{
          ????return?"b"
          }

          func?getBer()?Funer?{
          ????return?&B{}
          }

          func?main()?{
          ????b?:=?getBer()
          ????b.PrintName()
          }

          這段代碼的輸出應該是什么?考慮 20s 說出你的答案。

          這個實現中,正確的輸出的是 a,而通常在 C++,Java,Python 中這種思想下,我們給出的答案往往是 b,受到之前的語言思維習慣影響,那么 go ?的這個實現就會導致很多意想不到的事情。比如上面這位老哥遇到的詭異事情。

          這個問題的本質和這位老哥遇到的問題一樣,因為 Object 中嵌入了 Nested,所以有了 UnmarshalJSON, 符合了 json 包中 Unmarshaler 接口,所以內部用接口去處理的時候,Object 是滿足的,但實際處理的是 Nested,也就是以 Nested 作為實體來進行 UnmarshalJSON,導致了詭異的錯誤信息。

          如何解決

          解決這個問題的方式有很多種,這里給出一種比較穩(wěn)妥的思路:將嵌入字段的處理與其余字段分開,代碼如下:

          package?main

          import?(
          ?"encoding/json"
          ?"fmt"
          ?"time"
          )

          var?testJSON?=?`{"num":5,"duration":"5s"}`

          type?Nested?struct?{
          ?Dur?time.Duration?`json:"duration"`
          }

          func?(n?*Nested)?UnmarshalJSON(data?[]byte)?error?{
          ?*n?=?Nested{}
          ?tmp?:=?struct?{
          ??Dur?string?`json:"duration"`
          ?}{}
          ?fmt.Printf("parsing?nested?json?%s?\n",?string(data))
          ?if?err?:=?json.Unmarshal(data,?&tmp);?err?!=?nil?{
          ??fmt.Printf("failed?to?parse?nested:?%v",?err)
          ??return?err
          ?}
          ?tmpDur,?err?:=?time.ParseDuration(tmp.Dur)
          ?if?err?!=?nil?{
          ??fmt.Printf("failed?to?parse?duration:?%v",?err)
          ??return?err
          ?}
          ?(*n).Dur?=?tmpDur
          ?fmt.Printf("tmp?object:?%+v?\n",?tmp)
          ?return?nil
          }

          type?Object?struct?{
          ?Nested
          ?Num?int?`json:"num"`
          }

          //uncommenting?this?method?still?doesnt?help.
          //tmp?is?parsed?with?the?completed?json?at?Nested
          //which?doesnt?take?care?of?Num?field,?so?Num?is?zero?value.
          func?(o?*Object)?UnmarshalJSON(data?[]byte)?error?{
          ?tmp?:=?struct?{
          ??//Nested
          ??Num?int?`json:"num"`

          ?}{}
          ?//?unmarshal?Nested?alone
          ?tmpNest?:=?struct?{
          ??Nested
          ?}{}
          ?fmt.Printf("parsing?object?json?%s?\n",?string(data))
          ?if?err?:=?json.Unmarshal(data,?&tmp);?err?!=?nil?{
          ??fmt.Printf("failed?to?parse?object:?%v",?err)
          ??return?err
          ?}
          ?//?the?Nested?impl?UnmarshalJSON,?so?it?should?be?unmarshaled?alone
          ?if?err?:=?json.Unmarshal(data,?&tmpNest);?err?!=?nil?{
          ??fmt.Printf("failed?to?parse?object:?%v",?err)
          ??return?err
          ?}
          ?fmt.Printf("tmp?object:?%+v?\n",?tmp)
          ?(o).Num?=?tmp.Num
          ?(o).Nested?=?tmpNest.Nested
          ?return?nil
          }

          func?main()?{
          ?obj?:=?Object{}
          ?if?err?:=?json.Unmarshal([]byte(testJSON),?&obj);?err?!=?nil?{
          ??fmt.Printf("failed?to?parse?result:?%v",?err)
          ??return
          ?}
          ?fmt.Printf("result:?%+v?\n",?obj)
          }

          這樣就可以得到正確的自定義解析了。

          ps: 筆者在 golang/go ?的 issue 中搜了一下,發(fā)現早在 2016 年就有人踩過這個坑了,如今又有人踩到,遂寫下此文,勿再入坑。

          總結

          1. go 沒有繼承,也不要把面向對象的繼承思想直接用到 go 的代碼中,否則會遇到意想不到的 bug ;
          2. 結構體嵌入字段的實現方法的執(zhí)行順序要了解 - 從外層到內層。





          推薦閱讀



          學習交流 Go 語言,掃碼回復「進群」即可


          站長 polarisxu

          自己的原創(chuàng)文章

          不限于 Go 技術

          職場和創(chuàng)業(yè)經驗


          Go語言中文網

          每天為你

          分享 Go 知識

          Go愛好者值得關注



          瀏覽 122
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  97国产精品视频人人做人人爱 | 午夜免费激情视频 | 俺也去吧色影院 | 亚洲AV男人天堂 | 亚洲乱码精品 |