【问题标题】:Unmarshall nested interface{} into user supplied struct pointer将嵌套接口 {} 解组为用户提供的结构指针
【发布时间】:2017-04-30 15:33:49
【问题描述】:

我有一个 Set 函数,它将用户对象(或变量)包装在我自己的称为会话的结构中。它将它分配给我的会话结构的 Value 字段。 Set 函数然后编组这个结构并将字符串分配到存储中的某个位置。

我的问题是我不确定如何实现我的 Get 函数以仅返回存储在 Value 字段中的未编组结构,而不是整个会话包装结构。

我做了一个非常简单的example 来演示我在说什么。

我不能在我的 Get func 的赋值中使用类型断言,因为我事先不知道用户要使用什么类型。

我怀疑可能有一种方法可以使用反射来完成此操作?

编辑:到目前为止,提供的两个答案不是我想要的。我不知道用户将使用什么类型,它可能是任何类型,因此通过硬编码他们的类型或尝试“猜测”它可能包含的内容进行编码是行不通的。

【问题讨论】:

    标签: json go reflection


    【解决方案1】:

    好的,我想我知道你想要做什么。我找到了这个答案Converting map to struct 并进行了一些调整以使其适用于您的特定用例。注意:这还没有经过彻底测试,可能有点不稳定,使用风险自负:

    package main
    
    import (
        "bytes"
        "encoding/json"
        "errors"
        "fmt"
        "log"
        "reflect"
    )
    
    type session struct {
        Value interface{}
        Flash map[string]string
    }
    
    type Person struct {
        Name string
        Age  int
    }
    
    func Get(pointer interface{}) {
        marshalledString := `{"Value":{"Name":"bob","Age":3},"Flash":null}`
    
        var sess session
    
        d := json.NewDecoder(bytes.NewBuffer([]byte(marshalledString)))
        d.UseNumber()
        if err := d.Decode(&sess); err != nil {
            panic(err)
        }
    
        fmt.Printf("%#v", sess)
    
        switch sess.Value.(type) {
        case map[string]interface{}:
            err := FillStruct(sess.Value.(map[string]interface{}), pointer)
            if err != nil {
                log.Fatal(err)
            }
        default:
            return // You may want to return an error here...
        }
    }
    
    func main() {
        var personObj Person
    
        Get(&personObj)
    
        // Wanting to see personObj here have Name "bob" and Age 3
        fmt.Printf("%#v", personObj)
    }
    
    func SetField(obj interface{}, name string, value interface{}) error {
        structValue := reflect.ValueOf(obj).Elem()
        structFieldValue := structValue.FieldByName(name)
    
        if !structFieldValue.IsValid() {
            return fmt.Errorf("No such field: %s in obj", name)
        }
    
        if !structFieldValue.CanSet() {
            return fmt.Errorf("Cannot set %s field value", name)
        }
    
        structFieldType := structFieldValue.Type()
        val := reflect.ValueOf(value)
    
        if _, ok := value.(json.Number); ok {
            if f, err := value.(json.Number).Int64(); err == nil {
                structFieldValue.SetInt(f)
                return nil
            }
            if f, err := value.(json.Number).Float64(); err == nil {
                structFieldValue.SetFloat(f)
                return nil
            }
        }
    
        if structFieldType != val.Type() {
            return errors.New(fmt.Sprintf("Provided value type [%s] didn't match obj field type [%s]", val.Type().String(), structFieldType.String()))
        }
    
        structFieldValue.Set(val)
        return nil
    }
    
    func FillStruct(m map[string]interface{}, s interface{}) error {
        for k, v := range m {
            err := SetField(s, k, v)
            if err != nil {
                return err
            }
        }
        return nil
    }
    

    【讨论】:

    • 谢谢。这是一种解决方案,尽管我不确定它有多健壮(特别是如果您必须处理嵌套结构等)。我决定做的只是将用户结构存储为 json.RawMessage 而不是 interface{},以延迟解码,然后我解码两次而不是一次。第二个解码现在可以将 RawMessage 直接解码为用户指针。
    【解决方案2】:

    用户可以传入任何值,但您的代码可以通过将错误传回给他们来处理无效输入。如果您知道传入数据的所需格式,您可以直接解组它并单独处理任何无效输入。这消除了需要难以处理的中间接口{}:

    https://play.golang.org/p/VNCflbk3GL

    package main
    
    import (
        "encoding/json"
        "fmt"
    )
    
    type session struct {
        Value Person
        Flash map[string]string
    }
    
    type Person struct {
        Name string
        Age  int
    }
    
    func Get(marshaled string) (Person, error) {
        var sess session
        err := json.Unmarshal([]byte(marshaled), &sess)
        if err != nil {
            return Person{}, err
        }
        fmt.Println(sess) // {{bob 3} map[]}
        return sess.Value, nil
    }
    
    func main() {
        person, err := Get(`{"Value":{"Name":"bob","Age":3},"Flash":null}`)
        if err != nil {
            fmt.Println("Got err:", err)
        }
        fmt.Printf("%#v", person) // main.Person{Name:"bob", Age:3}
    }
    

    如果 Value 可以是多种类型是有效的,那么您将不得不在某处进行类型断言。不过在 Go 中并没有那么痛苦:

    https://newfivefour.com/golang-interface-type-assertions-switch.html

    switch v := anything.(type) {
    case string:
        fmt.Println(v)
    case int32, int64:
        fmt.Println(v)
    case SomeCustomType:
        fmt.Println(v)
    default:
        fmt.Println("unknown")
    }
    

    【讨论】:

    • 不幸的是我不知道入站类型,这就是我遇到这个问题的原因。传入的类型可以是任何东西,可以是结构,也可以是变量。这是用户自己定义的东西(这是用于 Web 框架库)。
    【解决方案3】:

    你的问题是你传入的 Value 数据类型是 map[string]interface{},Go 中没有直接/原生的方式将 map 转换为你的类型(虽然肯定有代码)。

    好的。如果我们假设我们完全无法控制 Value 字段中的传入数据,但我们仍然可以通过其属性的组合来识别数据类型,对吧?因为根据定义,您应该知道可能的选项。我们可以创建一个通用传入对象而不是 interface{}。 AWS 在他们的 Go SDK 中使用了类似的方法,至少对于 DynamoDB 服务,通过指针设置可选属性:https://github.com/aws/aws-sdk-go/blob/master/service/dynamodb/examples_test.go#L32

    因此,方法是:您的 UnknownObj 结构将具有可以在 json.Unmarshal 上填充(也可能不填充)的可选属性。知道通过交换机传递了哪些字段,您就可以猜测发送的数据。

    package main
    
    import (
        "encoding/json"
        "fmt"
    )
    
    type session struct {
        Value UnknownObj
        Flash map[string]string
    }
    
    type UnknownObj struct {
        Name           *string
        Age            *float64
        SomeOtherField *map[string]string
    }
    
    func Get() UnknownObj {
        marshalledString := `{"Value":{"Name":"bob","Age":3},"Flash":null}`
    
        var sess session
        json.Unmarshal([]byte(marshalledString), &sess)
        return sess.Value
    }
    
    func main() {
    
        v := Get()
    
        switch {
        case v.Name != nil && v.Age != nil:
            fmt.Println("This is a Person")
        default:
            fmt.Println("Unknown data type")
    
        }
    
    }
    

    但是,如果您可以控制 root/Values 字段,并且您可以请求向您发送每种类型的特定字段,而不是全部推送到 Values 下,那么您可以:

    type session struct {
        Person   *Person
        Car      *Car
        Building *Buidling
        Etc      *Etc
    
        ...
    }
    

    这样,您的解决方案将更加简单 -> 您只需要检查哪些属性不是 nil。

    希望这会有所帮助。

    干杯。

    2016 年 12 月 15 日更新 回复您对框架的评论:您所描述的是将用户请求绑定到任意数据类型的过程。 好的。不幸的是,这里发布的代码太多,但这里有一个链接作为起点: https://github.com/go-playground/validator/blob/v8.18.1/validator.go#L498

    这是 Gin 框架在此处用于绑定的包和方法:https://github.com/gin-gonic/gin/blob/master/binding/json.go

    祝你好运!

    【讨论】:

    • 不幸的是,我根本不知道他们可以传入什么。无论他们传入什么都是用户定义的,并且可以是任何东西(结构、变量、具有 1000 层的东西嵌套等)。这是一个 Web 框架库,因此用户可以定义任何类型的对象来存储在他们的会话中。
    • @b0xxed1n 知道了。请检查我上面的更新!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-08-16
    • 1970-01-01
    • 2012-09-23
    • 2023-03-10
    • 2018-04-17
    • 2010-11-14
    • 2015-01-26
    相关资源
    最近更新 更多