将嵌套接口{}解组为用户提供的结构指针

时间:2016-12-15 05:49:15

标签: json go reflection

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

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

我做了一个非常简单的example来展示我正在谈论的内容。

我无法在我的Get func中使用类型断言,因为我不知道用户将提前使用哪种类型。

我怀疑使用反射可能有办法实现这一目标吗?

编辑:到目前为止,我们提供的两个答案并不是我想要的。我不知道用户将使用什么类型,它可能是任何类型,所以通过硬编码他们的类型或尝试"猜测"来编码。它可能含有什么不起作用。

3 个答案:

答案 0 :(得分: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
}

答案 1 :(得分:0)

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

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")
}

答案 2 :(得分:0)

你的问题是你的传入数据类型的值是map [string] interface {},并且在Go中没有直接/本地方式将地图转换为你的类型(虽然那里肯定有代码)。

行。如果我们假设我们完全无法控制Value字段中的传入数据,但是,我们仍然可以通过其属性的组合来识别数据类型,对吧?因为根据定义,您应该知道可能的选项。我们可以创建一个通用的传入对​​象而不是接口{}。 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")

    }

}

但是,如果您可以控制根/值字段,并且可以请求为每个类型发送特定字段,而不是全部按下值,那么您可以:

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

    ...
}

这样,您的解决方案将更加轻松 - >你只需要检查什么属性不是零。

希望这有帮助。

干杯。

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

祝你好运!