英文:
Passing values to interface{}
问题
以下是翻译好的内容:
简短版
以下代码并没有按照预期的方式执行:
https://play.golang.org/p/sO4w4I_Lle
我猜我像往常一样搞乱了一些指针/引用的东西,但是我期望我的...
func unmarshalJSON(in []byte, s interface{}) error
...和 encoding/json
的...
func Unmarshal(data []byte, v interface{}) error
...表现出相同的方式(例如,更新作为第二个参数传递的引用)。
详细版
上面的示例是一个最小化的复现示例,没有太多意义。这是为了使其在 playground 上工作。然而,一个更具意义的、不那么简化的示例是这样的:
package main
import (
"fmt"
"gopkg.in/yaml.v2"
)
func unmarshalYAML(in []byte, s interface{}) error {
var result map[interface{}]interface{}
err := yaml.Unmarshal(in, &result)
s = cleanUpInterfaceMap(result)
// s is printed as expected
fmt.Println(s) // map[aoeu:[test aoeu] oaeu:[map[mahl:aoec tase:aoeu]]]
return err
}
func cleanUpInterfaceArray(in []interface{}) []interface{} {
out := make([]interface{}, len(in))
for i, v := range in {
out[i] = cleanUpMapValue(v)
}
return out
}
func cleanUpInterfaceMap(in map[interface{}]interface{}) map[string]interface{} {
out := make(map[string]interface{})
for k, v := range in {
out[fmt.Sprintf("%v", k)] = cleanUpMapValue(v)
}
return out
}
func cleanUpMapValue(v interface{}) interface{} {
switch v := v.(type) {
case []interface{}:
return cleanUpInterfaceArray(v)
case map[interface{}]interface{}:
return cleanUpInterfaceMap(v)
case string:
return v
default:
return fmt.Sprintf("%v", v)
}
}
func main() {
s := make(map[string]interface{})
b := []byte(`---
aoeu:
- test
- aoeu
oaeu:
- { tase: aoeu, mahl: aoec}
`)
err := unmarshalYAML(b, &s)
if err != nil {
panic(err)
}
// s is still an empty map
fmt.Println(s) // map[]
}
这个想法是将 YAML 反序列化为 map[string]interface{}
(而不是 map[interface{}]interface{}
),以便允许序列化为 JSON(其中标识符需要是字符串)。unmarshalYAML
函数应该提供与 yaml.Unmarshal
相同的函数签名...
英文:
Short
The following code does not exactly do what expected:
https://play.golang.org/p/sO4w4I_Lle
I assume that I mess up some pointer/reference stuff as usual, however I expect my...
func unmarshalJSON(in []byte, s interface{}) error
... and encoding/json
s...
func Unmarshal(data []byte, v interface{}) error
...to behave the same way (eg. update the referenced passed as second argument).
Long
The example above is a minimal reproducer that does not make much sense. This is in order to make it work on the playground. However, an less minimal example that does make sense is this:
package main
import (
"fmt"
"gopkg.in/yaml.v2"
)
func unmarshalYAML(in []byte, s interface{}) error {
var result map[interface{}]interface{}
err := yaml.Unmarshal(in, &result)
s = cleanUpInterfaceMap(result)
// s is printed as expected
fmt.Println(s) // map[aoeu:[test aoeu] oaeu:[map[mahl:aoec tase:aoeu]]]
return err
}
func cleanUpInterfaceArray(in []interface{}) []interface{} {
out := make([]interface{}, len(in))
for i, v := range in {
out[i] = cleanUpMapValue(v)
}
return out
}
func cleanUpInterfaceMap(in map[interface{}]interface{}) map[string]interface{} {
out := make(map[string]interface{})
for k, v := range in {
out[fmt.Sprintf("%v", k)] = cleanUpMapValue(v)
}
return out
}
func cleanUpMapValue(v interface{}) interface{} {
switch v := v.(type) {
case []interface{}:
return cleanUpInterfaceArray(v)
case map[interface{}]interface{}:
return cleanUpInterfaceMap(v)
case string:
return v
default:
return fmt.Sprintf("%v", v)
}
}
func main() {
s := make(map[string]interface{})
b := []byte(`---
aoeu:
- test
- aoeu
oaeu:
- { tase: aoeu, mahl: aoec}
`)
err := unmarshalYAML(b, &s)
if err != nil {
panic(err)
}
// s is still an empty map
fmt.Println(s) // map[]
}
The idea is to unmarshal YAML to map[string]interface{}
(instead of map[interface{}]interface{}
) is order to allow to serialize to JSON (where identifiers need to be strings). The unmarshalYAML
function should provide the same func signture as yaml.Unmarshal
...
答案1
得分: 4
使用类型断言
在你的unmarshalJSON()
函数中,参数s
的行为类似于一个局部变量。当你给它赋值时:
s = result
它只会改变局部变量的值。
由于你希望它能够改变*map[string]interface{}
的值,并且这就是你传递给它的内容,你可以使用一个简单的类型断言来获取该指针,并将该指针传递给json.Unmarshal()
:
func unmarshalJSON(in []byte, s interface{}) error {
if m, ok := s.(*map[string]interface{}); !ok {
return errors.New("Expecting *map[string]interface{}")
} else {
return json.Unmarshal(in, m)
}
}
在Go Playground上尝试修改后的工作示例。
只是简单地传递
还要注意,这完全是不必要的,因为json.Unmarshal()
也被定义为接受类型为interface{}
的目标,与你所拥有的相同。所以你甚至不需要做任何事情,只需简单地传递它:
func unmarshalJSON(in []byte, s interface{}) error {
return json.Unmarshal(in, s)
}
在Go Playground上尝试这个。
使用函数类型的变量
有趣的是,你的unmarshalJSON()
的_签名_和库函数json.Unmarshal()
是相同的:
// Yours:
func unmarshalJSON(in []byte, s interface{}) error
// json package
func Unmarshal(data []byte, v interface{}) error
这意味着还有另一种选择,就是你可以使用一个名为unmarshalJSON
的函数类型的变量,并简单地将函数值json.Unmarshal
赋给它:
var unmarshalJSON func([]byte, interface{}) error = json.Unmarshal
现在你有一个函数类型的变量unmarshalJSON
,你可以像调用函数一样调用它:
err := unmarshalJSON(b, &s)
在Go Playground上尝试这个函数值。
现在来看看你的unmarshalYAML()
函数
在你的unmarshalYAML()
函数中,你犯了同样的错误:
s = cleanUpInterfaceMap(result)
这只会改变你的局部变量s
(参数)的值,并不会“填充”传递给unmarshalYAML()
的映射(指针)。
使用上面详细介绍的类型断言技术来从s
的interface{}
参数中获取指针,一旦你拥有了指针,你就可以改变指向的对象(“外部”映射)。
func unmarshalYAML(in []byte, s interface{}) error {
var dest *map[string]interface{}
var ok bool
if dest, ok = s.(*map[string]interface{}); !ok {
return errors.New("Expecting *map[string]interface{}")
}
var result map[interface{}]interface{}
if err := yaml.Unmarshal(in, &result); err != nil {
return err
}
m := cleanUpInterfaceMap(result)
// m保存了结果,dest是传递给我们的指针,
// 我们可以直接设置指向的对象(映射):
*dest = m
return nil
}
英文:
Using Type assertion
Inside your unmarshalJSON()
function the parameter s
behaves like a local variable. When you assign something to it:
s = result
It will only change the value of the local variable.
Since you want it to work with changing the value of a *map[string]interface{}
and that is what you pass to it, you could use a simple type assertion to obtain the map pointer from it, and pass this pointer to json.Unmarshal()
:
func unmarshalJSON(in []byte, s interface{}) error {
if m, ok := s.(*map[string]interface{}); !ok {
return errors.New("Expecting *map[string]interface{}")
} else {
return json.Unmarshal(in, m)
}
}
Try your modified, working example on the Go Playground.
Just passing it along
Also note that however this is completely unnecessary as json.Unmarshal()
is also defined to take the destination as a value of type interface{}
, the same thing you have. So you don't even have to do anything just pass it along:
func unmarshalJSON(in []byte, s interface{}) error {
return json.Unmarshal(in, s)
}
Try this on the Go Playground.
With a variable of function type
As an interesting thing note that the signature of your unmarshalJSON()
and the library function json.Unmarshal()
is identical:
// Yours:
func unmarshalJSON(in []byte, s interface{}) error
// json package
func Unmarshal(data []byte, v interface{}) error
This means there is another option, that is you could use a variable named unmarshalJSON
of a function type, and just simply assign the function value json.Unmarshal
:
var unmarshalJSON func([]byte, interface{}) error = json.Unmarshal
Now you have a variable unmarshalJSON
which is of function type, and you can call it as if it would be a function:
err := unmarshalJSON(b, &s)
Try this function value on the Go Playground.
Now on to your unmarshalYAML()
function
In your unmarshalYAML()
you do the same mistake:
s = cleanUpInterfaceMap(result)
This will only change the value of your local s
variable (parameter), and it will not "populate" the map (pointer) passed to unmarshalYAML()
.
Use the type assertion technique detailed above to obtain the pointer from the s
interface{}
argument, and once you have that, you can change the pointed object (the "outside" map).
func unmarshalYAML(in []byte, s interface{}) error {
var dest *map[string]interface{}
var ok bool
if dest, ok = s.(*map[string]interface{}); !ok {
return errors.New("Expecting *map[string]interface{}")
}
var result map[interface{}]interface{}
if err := yaml.Unmarshal(in, &result); err != nil {
return err
}
m := cleanUpInterfaceMap(result)
// m holds the results, dest is the pointer that was passed to us,
// we can just set the pointed object (map):
*dest = m
return nil
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论