英文:
Using function to satisfy interface via method
问题
我正在尝试编写一个方法,该方法将返回一个满足json.Marshaler
接口的函数。我的想法是为结构体提供不同的表示形式。也许我完全错误地处理了这个问题。
func (api *Api) SiteList(c *gin.Context) {
var sites []db.Site
if err := api.db.Find(&sites).Error; err != nil {
}
var payload []json.Marshaler
for _, site := range sites {
payload = append(payload, site.ToApi())
}
c.JSON(http.StatusOK, payload)
}
我从这个函数得到的结果是列表中的正确数量的项,但是每个项的值都相同:
[
{
"key": "NZ7LCA9HQN3",
"name": "autumn-waterfall-1573"
},
{
"key": "NZ7LCA9HQN3",
"name": "autumn-waterfall-1573"
},
{
"key": "NZ7LCA9HQN3",
"name": "autumn-waterfall-1573"
},
{
"key": "NZ7LCA9HQN3",
"name": "autumn-waterfall-1573"
},
{
"key": "NZ7LCA9HQN3",
"name": "autumn-waterfall-1573"
}
]
最后,这是ToApi
的实现:
type EncoderFunc func() ([]byte, error)
func (fn EncoderFunc) MarshalJSON() ([]byte, error) {
return fn()
}
func (site *Site) ToApi() json.Marshaler {
return EncoderFunc(func() ([]byte, error) {
var payload public.Site
payload.Name = site.Name
payload.Key = site.Key
data, err := json.Marshal(payload)
if err != nil {
return nil, err
}
return data, nil
})
}
英文:
I am trying to write a method that will return a function that can satisfy the json.Marshaler
interface. My reasoning is to provide different representations of the struct. Perhaps I am approaching this completely wrong.
func (api *Api) SiteList(c *gin.Context) {
var sites []db.Site
if err := api.db.Find(&sites).Error; err != nil {
}
var payload []json.Marshaler
for _, site := range sites {
payload = append(payload, site.ToApi())
}
c.JSON(http.StatusOK, payload)
}
The result I get from this function is the correct number of items in the list, but the same value for each:
[
{
"key": "NZ7LCA9HQN3",
"name": "autumn-waterfall-1573"
},
{
"key": "NZ7LCA9HQN3",
"name": "autumn-waterfall-1573"
},
{
"key": "NZ7LCA9HQN3",
"name": "autumn-waterfall-1573"
},
{
"key": "NZ7LCA9HQN3",
"name": "autumn-waterfall-1573"
},
{
"key": "NZ7LCA9HQN3",
"name": "autumn-waterfall-1573"
}
]
Finally, here is the ToApi
implementation:
type EncoderFunc func() ([]byte, error)
func (fn EncoderFunc) MarshalJSON() ([]byte, error) {
return fn()
}
func (site *Site) ToApi() json.Marshaler {
return EncoderFunc(func() ([]byte, error) {
var payload public.Site
payload.Name = site.Name
payload.Key = site.Key
data, err := json.Marshal(payload)
if err != nil {
return nil, err
}
return data, nil
})
}
答案1
得分: 5
这似乎是一个经典的闭包陷阱。有一个相关的FAQ部分。
基本上,在你的for循环中,site
每次都有相同的地址。所有的函数都是在这个地址上闭包的。所以当你在for循环之后评估它时,它将重复在相同(最后一个)地址上调用MarshalJSON
。你可以通过在每次迭代时创建一个新值来纠正这个问题:
for _, site := range sites {
site := site // 完全合法和惯用的写法。
payload = append(payload, site.ToApi())
}
Playground: https://play.golang.org/p/eFujC1hEyD
另一个相关的文档片段来自Effective Go:
这个bug在Go的for循环中,循环变量在每次迭代中被重用,所以req变量在所有goroutine之间是共享的。这不是我们想要的。(...) 另一个解决方案是只需创建一个同名的新变量,就像这个例子中一样:
for req := range queue {
req := req // 为goroutine创建req的新实例。
sem <- 1
go func() {
process(req)
<-sem
}()
}
这个部分是关于goroutines的,但显然也适用于所有的闭包。
英文:
This seems like a classic closure gotcha. There is a related FAQ section.
Basically, site
in your for-loop has the same address every time. All your functions are closed over this address. So when you evaluate it after the for-loop, it will repeatedly call MarshalJSON
on the value on the same (last) address. You can correct that by creating a new value on every iteration:
for _, site := range sites {
site := site // Perfectly legal and idiomatic.
payload = append(payload, site.ToApi())
}
Playground: https://play.golang.org/p/eFujC1hEyD
Another related piece of documentation from Effective Go:
>The bug is that in a Go for loop, the loop variable is reused for each iteration, so the req variable is shared across all goroutines. That's not what we want. (...) Another solution is just to create a new variable with the same name, as in this example:
>
for req := range queue {
req := req // Create new instance of req for the goroutine.
sem <- 1
go func() {
process(req)
<-sem
}()
}
This section is about goroutines, but apparently this also applies to all closures.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论