【问题标题】:Cast interface to concrete type - type switch铸造接口到具体类型 - 类型开关
【发布时间】:2021-05-20 04:54:50
【问题描述】:

我正在寻找一种将接口转换为具体类型以节省大量源代码的方法。

初始情况是网络服务器处理程序的两个功能。它们的区别仅在于一个函数解码结构数组,而另一个函数解码单个结构并将其存储在数据库中。根据类型不同,需要调用的函数是相同的。

要决定传递的是数组还是结构体,它会尝试将接口转换为类型,然后将其作为函数的参数适当地传递。与 documentation 和 stackoverflow post 中描述的类似。

但是,我没有得到预期的具体类型,程序总是运行到默认部分。我做错了什么或没有考虑到什么?

这些是默认部分的输出:

# interface is a struct
... or a single repository struct: map[string]interface{}

# interface is an array of structs
... or a single repository struct: []interface{}

下面是函数的源代码

func (rh *RouteHandler) AddOrUpdateRepository(rw http.ResponseWriter, req *http.Request) {
    repository := new(types.Repository)
    rh.addOrUpdateRepositories(rw, req, repository)
}

func (rh *RouteHandler) AddOrUpdateRepositories(rw http.ResponseWriter, req *http.Request) {
    repositories := make([]*types.Repository, 0)
    rh.addOrUpdateRepositories(rw, req, repositories)
}

func (rh *RouteHandler) addOrUpdateRepositories(rw http.ResponseWriter, req *http.Request, v interface{}) {
    defer req.Body.Close()

    switch req.Header.Get("Content-Type") {
    case "application/xml":
        xmlDecoder := xml.NewDecoder(req.Body)
        err := xmlDecoder.Decode(&v)
        if err != nil {
            rw.WriteHeader(http.StatusInternalServerError)
            fmt.Fprintf(rw, "Failed to decode repositories or repository")
            rh.ulogger.Error("Failed to decode repositories or repository: %v", err)
            return
        }
    case "application/json":
        fallthrough
    default:
        jsonDecoder := json.NewDecoder(req.Body)
        err := jsonDecoder.Decode(&v)
        if err != nil {
            rw.WriteHeader(http.StatusInternalServerError)
            fmt.Fprintf(rw, "Failed to decode repositories or repository")
            rh.ulogger.Error("Failed to decode repositories or repository: %v", err)
            return
        }
    }

    var err error
    switch x := v.(type) {
    case map[string]*types.Repository:
        for _, repository := range x {
            err = rh.manager.AddOrUpdateRepository(context.Background(), repository)
        }
    case *types.Repository:
        err = rh.manager.AddOrUpdateRepository(context.Background(), x)
    case map[string][]*types.Repository:
        for i := range x {
            for j := range x[i] {
                err = rh.manager.AddOrUpdateRepository(context.Background(), x[i][j])
            }
        }
    case []*types.Repository:
        err = rh.manager.AddOrUpdateRepository(context.Background(), x...)
    case nil:
        rw.WriteHeader(http.StatusInternalServerError)
        fmt.Fprintf(rw, "Failed to cast interface")
        rh.ulogger.Error("Failed to cast interface. Interface is a type of nil")
        return
    default:
        rw.WriteHeader(http.StatusInternalServerError)
        fmt.Fprintf(rw, "Failed to cast interface")
        rh.ulogger.Error("Failed to cast interface. Interface does not match onto an array of repositories or a single repository struct: %T", x)
        return
    }

    if err != nil {
        rw.WriteHeader(http.StatusInternalServerError)
        fmt.Fprintf(rw, "Failed to add repositories or repository")
        rh.ulogger.Error("Failed to add repositories or repository: %v", err)
        return
    }
    rw.WriteHeader(http.StatusCreated)

}

【问题讨论】:

  • 注意:Go 根本没有类型转换。您所追求的是类型转换。
  • “一个函数解码一个结构数组”——另一个术语说明:这不是一个数组。这是一片。
  • 您可能试图断言错误的类型。为了使调试更容易,请考虑在您的default 案例中添加类似这样的内容:fmt.Fprintf(rw, "Failed to assert type %T", v),这将显示您遇到的实际类型。

标签: go interface casting


【解决方案1】:

(简化了一点。)

您有一个具有以下签名的函数:

func addOrUpdateRepositories(v interface{})

然后你这样称呼它:

repository := new(types.Repository)
addOrUpdateRepositories(repository)

像这样:

repositories := make([]*types.Repository, 0)
addOrUpdateRepositories(repositories)

在第一次调用中,存储在v 中的值的具体类型将是*types.Repository(因为new 返回指向分配值的指针),在第二次调用中,存储在中的值的具体类型v 将是 []*types.Repository——因为这是 make 被告知要创建的。

现在您在v 上进行类型切换,内容如下:

switch x := v.(type) {
case map[string]*types.Repository:
case map[string][]*types.Repository:
case nil:
default:
}

暂且不说,如果您不调用 addOrUpdateRepositories 将其传递给 nil v 这在您的问题的 sn-p 中不会发生,switch 将始终选择默认分支,因为类型存储在v 中的具体值永远不会是map[string]*types.Repositorymap[string][]*types.Repository

我不确定您为什么看不到这一点,因此您可能应该完善您的问题,或者尝试在对我的回答的评论中消除您的困惑?


另一个黑暗中的镜头:类型转换(请注意,Go 没有类型转换,正如 @Flimzy 指出的那样)和 Go 中的类型切换实际上并没有改变它们所操作的值的底层表示——除了有限的一组(“每个人都期望这个”)案例,例如将 float64 类型转换为 int64,这些案例都有详细记录。

因此,您不能将[]*types.Repository(指向types.Repository 类型值的指针切片)以某种方式强制它“变为”map[string][]*types.Repository:出于多种原因,这样做是荒谬的,最引人注目的是:如果你正在编写 Go 编译器,你将如何进行这样的“类型转换”?假设您要真正分配一个映射,但是应该为该映射中的哪个键分配原始(源)切片?将[]*types.Repository 类型转换为struct {foo []*types.Repository; bar []*types.Repository} 怎么样?

【讨论】:

  • 我已经使用*types.Repository[]*types.Repository作为case语句,但是程序总是默认运行。通过%T 我得到接口的类型。我得到*types.Repository []interface*types.Repository map[string]interface{} 的一部分。我无法解释为什么它仍然在强制转换后将接口作为类型。
  • @VolkerRaschek,您的(整个)程序中存在逻辑错误。一个严重愚蠢的MCVE 表明真的没有魔法:play.golang.org/p/47LM45kMIiO
  • 是的,我已经在一个小项目中尝试过了。这就是为什么我完全被激怒了,因为它起作用了。我现在发现是因为源码解码json或者xml。解码后接口 v 与预期不匹配。这是解码json后在默认部分运行的程序的截图。我已经重新创建了整个东西:play.golang.org/p/Z2aCIHZAPk9
  • 我必须先复制接口,然后才能将其传递给解码器。否则就坏了:play.golang.org/p/YqnM26biKrV
  • @VolkerRaschek,好的,我知道会发生什么。稍后我会尝试发布另一个答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-12-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-10
  • 1970-01-01
相关资源
最近更新 更多