【问题标题】:Passing values to interface{}将值传递给接口{}
【发布时间】:2016-02-17 06:31:40
【问题描述】:

以下代码并未完全符合预期: https://play.golang.org/p/sO4w4I_Lle

我假设我像往常一样搞砸了一些指针/引用的东西,但是我希望我的...

func unmarshalJSON(in []byte, s interface{}) error

...和encoding/jsons...

func Unmarshal(data []byte, v interface{}) error 

...以相同的方式表现(例如,更新作为第二个参数传递的引用)。

上面的例子是一个没有多大意义的最小复制器。这是为了让它在操场上工作。然而,一个不那么简单但有意义的例子是:

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 相同的函数签名...

【问题讨论】:

    标签: pointers go interface yaml unmarshalling


    【解决方案1】:

    使用类型断言

    unmarshalJSON() 函数中,参数s 的行为类似于局部变量。当你给它赋值时:

    s = result
    

    它只会改变局部变量的值。

    由于您希望它与更改 *map[string]interface{} 的值一起工作,这就是您传递给它的值,您可以使用简单的 type assertion 从中获取映射指针,并将此指针传递给 @987654331 @:

    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
    

    这意味着还有另一种选择,即您可以使用function type 的一个名为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{} 参数中获取指针,一旦你有了它,你就可以改变pointed 对象(“外部”映射) .

    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
    }
    

    【讨论】:

    • 谢谢@icza!您介意看一下我提供的“长”示例吗?我明白你在指出什么,但还没有找到将其应用于 unmarshalYAML() 的方法......
    • @sontags 但这正是发生的事情。或多或少。在上面的示例中,我不检查是否传递了映射或 nil 映射值,它只会“覆盖”传递的映射变量。但它会从外面看到。
    • @sontags 此外,您可以检查 *dest 是否不是 nil,如果不是,您可以向该映射添加(复制)值,但更清楚的是,只分配该映射可能已经包含值,从而进一步混淆这些元素的来源(例如,已经在地图中,或者是解组的结果)。
    • 一切都好!我花了很长时间才写我的评论,同时你更新了你的答案!非常感谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-08-29
    • 2012-08-29
    • 2017-11-23
    • 2010-10-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多