Golang:在结构体切片之间进行类型转换

6
这个问题是我之前另一个问题 的延伸。
以下是我的测试代码,我不太明白为什么我的尝试将 res 转换为 ListSociete 会出错:
import (
    "errors"
    "fmt"
    "github.com/jmcvetta/neoism"
)

type Societe struct {
    Name string
}

type ListSociete []Societe

func loadListSociete(name string) (ListSociete, error) {
    db, err := neoism.Connect("http://localhost:7474/db/data")
    if err != nil {
        return nil, err
    }
    res := []struct {
        Name string `json:"a.name"`
    }{}
    cq := neoism.CypherQuery{
        Statement: `
            MATCH (a:Societe)
            WHERE a.name = {name}
            RETURN a.name
            `,
        Parameters: neoism.Props{"name": name},
        Result:     &res,
    }
    db.Cypher(&cq)
    if len(res) == 0 {
        return nil, errors.New("Page duz not exists")
    }
    r := res[0]
    return ListSociete(res), nil
}

一个[]struct{Name string}和一个[]struct{Name string `json:"a.name"`}不同吗?

或者说一个ListSociete与一个[]struct{Name string}不同吗?

谢谢。


1
json:"a.name" 添加到 Societe 中是否有效?另外,使用 res := Societe{} 有什么问题吗? - OneOfOne
2
什么是错误?输出是什么? - fabrizioM
抱歉,我忘记了。这是:无法将res(类型[]struct { Name string "json:\"a.name\"" })转换为ListSociete类型 - Nicolas Marshall
@OneOfOne 做了一些测试,将json标签添加到Societe上不起作用。而ListSociete似乎与[]struct{Name string}不同。 - Nicolas Marshall
在定义res := Societe{}时,这并不是一个真正的选项,因为我需要标签从我的数据库查询中提取结果(而且Societe只是一个结构体,而res是一个结构体切片)。 - Nicolas Marshall
2个回答

11

您目前正在处理两种不同类型:

type Societe struct {
    Name string
}

还有匿名的一个:

struct {
    Name string `json:"a.name"`
}

如果没有标签,这两个结构体类型将是相同的。 Go规范 指出(我强调):

如果两个结构体类型具有相同的字段顺序,并且相应的字段具有相同的名称、相同的类型和相同的标签,则它们是相同的。两个匿名字段被视为具有相同的名称。来自不同包的小写字段名称始终是不同的。

因此,您不能在两者之间进行简单的转换。而且,您正在转换这两种类型的 slices 使得转换问题更加复杂。我可以看到您有两个选项:

通过迭代复制:

这是一个安全且推荐的解决方案,但也更加冗长和缓慢。

ls := make(ListSociete, len(res))
for i := 0; i < len(res); i++ { 
    ls[i].Name = res[i].Name
}
return ls, nil

不安全的类型转换:

由于两种类型具有相同的基础数据结构,因此可能进行不安全的类型转换。
然而,这可能会在以后出现问题。请注意!

return *(*ListSociete)(unsafe.Pointer(&res)), nil
示例代码: http://play.golang.org/p/lfk7qBp2Gb (涉及编程相关内容,请自行查看)

尽管可以在仅标签不同的结构体之间进行转换,但无法在这些结构体的切片之间进行转换。为什么?请参见 https://play.golang.org/p/RSRoPnvXXU7 - Peter Dotchev
这似乎是一种语言设计缺陷。你需要复制切片内容 - 这会对性能造成重大影响,或者使用不安全的内存指针。这两种方法都违反了Go的指导原则。 - Peter Dotchev
你需要复制切片内容。那么空切片呢?如果需要,将一个空结构体切片复制到一个空接口切片中。 - Roman Golubin
这个答案在当时是正确的,但从Go 1.8(2017年2月)开始,这个限制已经被移除,因此两个仅在标签上不同的结构体现在可以从一个类型转换为另一个类型,允许这种赋值。然而,转换仍然必须是显式的:它们仍然被视为不同的类型。请参见https://beta.golang.org/doc/go1.8#language - JVMATL
(针对我之前的评论进行扩展:您仍然无法一次性分配整个不同结构类型的切片,但现在您可以一次性分配整个“兼容”的结构体,而不必复制每个结构体的单个元素) - JVMATL

1

经过一些测试,我发现以下结果:

定义为ListSociete的列表如下...

type Societe struct {
    Name string `json:"a.name"`
}

type ListSociete []Societe

不同于这个:

type ListSociete []struct {
    Name string `json:"a.name"`
}

第二种解决方案可行,而第一种则不行。

因此,我认为没有直接转换不同标签类型的方法(除非编写显式循环)?

在这种情况下,我肯定会选择循环,因为直接在类型中使用标签(参见上面的第二种解决方案)会使我的代码难以阅读和重用,而且我真的不知道使用不安全的转换方法会有什么问题。所以感谢您确认了不同的标签会产生不同的类型。


循环工作得非常好,顺便说一下。它真的会慢那么多吗?(我是指执行速度) - Nicolas Marshall
这取决于列表的大小。不安全的转换是O(1)操作,而循环是O(N)。大多数情况下,迭代速度足够快。奇怪的是它对你没起作用。在**playground example**中它运行得很好。 - ANisus
我一定是哪里出错了。我会再试着让它工作起来的。 - Nicolas Marshall

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接