go interface 使用 與 marshal 使用


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

使用過程中需要注意的幾點:

  1. marshal 過程中,對應的struct結構體必須首字母大寫!
  2. 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 斷言會報錯。

解決的方式有兩種:

  1. 將其作為 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)
}
  1. 先將這個 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 字段的方法(就像強行轉換空指針,雖然感覺很危險),留待以后學習吧。

參考鏈接:

Go語言Interface漫談

GO語言Comma-ok斷言


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM