go interface 與 marshal 使用
Interface 基本使用
// _Interfaces_ are named collections of method
// signatures.
package main
import "fmt"
import "math"
// Here's a basic interface for geometric shapes.
type geometry interface {
area() float64
perim() float64
}
// For our example we'll implement this interface on
// `rect` and `circle` types.
type rect struct {
width, height float64
}
type circle struct {
radius float64
}
// To implement an interface in Go, we just need to
// implement all the methods in the interface. Here we
// implement `geometry` on `rect`s.
func (r rect) area() float64 {
return r.width * r.height
}
func (r rect) perim() float64 {
return 2*r.width + 2*r.height
}
// The implementation for `circle`s.
func (c circle) area() float64 {
return math.Pi * c.radius * c.radius
}
func (c circle) perim() float64 {
return 2 * math.Pi * c.radius
}
// If a variable has an interface type, then we can call
// methods that are in the named interface. Here's a
// generic `measure` function taking advantage of this
// to work on any `geometry`.
func measure(g geometry) {
fmt.Println(g)
fmt.Println(g.area())
fmt.Println(g.perim())
}
func main() {
r := rect{width: 3, height: 4}
c := circle{radius: 5}
// The `circle` and `rect` struct types both
// implement the `geometry` interface so we can use
// instances of
// these structs as arguments to `measure`.
measure(r)
measure(c)
}
這個例子的官方解釋是:
矩形和圓形都實現了幾何圖形的接口
我來做幾點解釋:
- measure 的參數是一個 geometry 的接口(interface)
- 當調用 measure 函數的時候會有一次類型轉換,將實參轉換為形參接口類型
- 這個轉換過程是在編譯期間完成的,編譯器會檢測方法列表,當實參方法列表是形參方法列表的超集時,此次轉換成功
空 Interface
上面那個例子是有方法的interface作為參數,但是很多時候還會出現一種空interface
空interface(interface{})不包含任何的method,因此所有的類型都實現了空interface。
https://tiancaiamao.gitbooks.io/go-internals/content/zh/07.2.html interface 原理
因此空Interface就很像C語言里面的空指針。
comma-ok
但是不同的是,接口包含的是方法。並且空接口也不是直接強制轉換為其他接口的,而是通過如下方法:
func Disconnect(usb interface{}){ //注意,這里是空接口
switch v:=usb.(type) {
case PhoneConnect:
fmt.Println(" Phone device Disconnected from",v.name)
case TVConnect:
fmt.Println("TV device Disconnected from",v.name)
default:
fmt.Println("Unknown device ...")
}
}
func main(){
a := PhoneConnect{"IPhone"}
b := TVConnect{"ChuangWei"}
Disconnect(a)
Disconnect(b)
}
這種方式被稱為Comma-ok斷言
Comma-ok斷言的語法是:value, ok := element.(T)。element必須是接口類型的變量,T是普通類型
如果element是T類型的數據,那么斷言成功(interface{} 的 _type 字段匹配),轉換為value對象。否則OK被置為false
還有一種switch語法就如同上面例子中使用的一樣。
interface slice
interface slice 與 萬能類型 empty interface 是不一樣的,可以直接將任何類型的值傳給萬能類型,但是不能將任何類型的 slice 直接傳給 interface slice
舉例說明:
func MethodTakeinSlice(in []interface{}){...}
...
slice := []int{1,2,3}
MethodTakeinSlice(slice) // 會報錯 cann't use slic (type []int) as type []interface{}
marshal
一個簡單的介紹 https://www.jb51.net/article/130778.htm
http://cizixs.com/2016/12/19/golang-json-guide
使用過程中需要注意的幾點:
- marshal 過程中,對應的struct結構體必須首字母大寫!
- unmarshal 函數傳遞的是 地址!需要加上&符號
記一次 json marshal 過程
某次測試的過程中遇到這樣一個問題:
我們的某個接口,訪問網頁,獲取到了返回值,將其 unmarshal 為了一種特殊的結構體
req, err := http.NewRequest(method, t.endpoint+urlPath, bytes.NewReader(bodyData))
resp, err := t.client.Do(req) // resp 為 *http.Response 類型
//type Response struct {
// Code int `json:"code"`
// Message string `json:"message"`
// Data interface{} `json:"data"` // api-specified response result
//}
res := new(hh.Response)
err = hh.UnmarshalStream(marshaler.NewJsonMarshaler(), resp.Body, res)
// res 為 Response 類型
然后我需要將 res.Data 轉換成我需要的結構體數組類型。
一開始試來試去,弄出一種方法:
es_status := types.ES_health{}
json.Unmarshal([]byte(resp.Data.(string)), &es_status)
也就是先將 resp.Data 聲明為string(居然沒有報錯),然后轉換為 []byte,最后unmarshal。
這個方法對於這個例子還是OK的。
但是,當返回值並不是直接的結構體,而是結構體數組的時候,這種方法就行不通了。
例如:
var val []model.Flavor
json.Unmarshal([]byte(resp.Data.(string)), &val)
報錯
panic: interface conversion: interface {} is map[string]interface {}, not string
省去分析過程了,直接上結論
結論
marshal 會將 []byte 代表的 json 數據轉換為我們想要的結構體,但是一旦我們轉換的目標結構體中包含 interface{} 字段的時候,marshal 便無法分析這段數據的類型,將其轉換為默認的 map[string]interface{}。也就是這個 interface{} 數據指針的 _type 成員未被設置。因此之后的 comma-ok 斷言會報錯。
解決的方式有兩種:
- 將其作為 map[string]interface{} 使用,並且調用庫mapstructure(需要下載)將 interface{} 轉換成我們需要的結構體
for _, v := range res.Data.([]interface{}){
val2 := model.Flavor{}
if err := mapstructure.Decode(v, &val2); err != nil {
t.Fatal(err)
}
t.Log(val2)
}
- 先將這個 interface{} 使用marshal 函數轉換為 []byte,然后再調用 unmarshal 將其轉換為我們需要的 struct,也就是說,unmarshal 的時候傳入我們的 struct 變量,這樣unmarshal 函數就會幫我們填充 _type 字段了
var val []model.Flavor
v, err:= json.Marshal(res.Data)
if err!= nil {
t.Fatal(err)
}
json.Unmarshal(v, &val)
t.Log(val)
由於第一種方法會引入額外的依賴,因此第二種方式更優一點。
我覺得應該有直接修改 interface{} 的_type 字段的方法(就像強行轉換空指針,雖然感覺很危險),留待以后學習吧。
參考鏈接:
