将接口转换为具体类型 - 类型开关

Cast interface to concrete type - type switch

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

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

为了决定传递的是数组还是结构,尝试将接口转换为类型,然后将其适当地作为函数的参数传递。与 documentation and in the Whosebug .

中描述的类似

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

这些是默认部分的输出:

# 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)

}

(简化了一点。)

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

func addOrUpdateRepositories(v interface{})

然后你这样称呼它:

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

像这样:

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

在第一次调用中,存储在 v 中的值的具体类型将是 *types.Repository (因为 new returns 指向分配值的指针)和在第二次调用中,存储在 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 这在您的问题的片段中不会发生的情况,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} 怎么样?