简体   繁体   English

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

[英]Unmarshall nested interface{} into user supplied struct pointer

I've got a Set function that wraps a users object (or variable) in my own struct called sessions. 我有一个Set函数,该函数将一个用户对象(或变量)包装在我自己的称为会话的结构中。 It assigns it to the Value field of my sessions struct. 它将其分配给我的会话结构的“值”字段。 The Set function then marshalls this struct and assigns the string somewhere in storage. 然后,Set函数将编组此结构并将该字符串分配给存储中的某个位置。

My problem is that I'm not sure how to implement my Get function to only return the unmarshalled struct stored in the Value field, opposed to the entire sessions wrapper struct. 我的问题是我不确定如何实现Get函数仅返回存储在Value字段中的未编组结构,而不是整个会话包装器结构。

I've made a very simple example demonstrating what I'm talking about. 我已经做了一个非常简单的例子来说明我在说什么。

I can't use a type assertion in the assignment in my Get func because I don't know what type the user is going to use in advance. 我不能在Get func的赋值中使用类型断言,因为我不知道用户会预先使用哪种类型。

I suspect there may be a way using reflection to accomplish this? 我怀疑可能有一种使用反射的方法来完成此操作?

Edit: The two provided answers so far are not what I'm looking for. 编辑:到目前为止提供的两个答案不是我想要的。 I do not know what type the user will be using, it could be anything, so coding around that by hard coding their type or trying to "guess" what it may contain is not going to work. 我不知道用户将使用哪种类型,可以是任何类型,因此通过对他们的类型进行硬编码或试图“猜测”其中可能包含的内容来进行编码是行不通的。

OK, I think I know what you're wanting to do. 好吧,我想我知道你想做什么。 I found this answer Converting map to struct and made some tweaks to get it working for your particular use case. 我找到了这个将map转换为struct的答案并做了一些调整,以使其适合您的特定用例。 Note: this hasn't been tested thoroughly and may be a little shaky, use at your own risk: 注意:此功能尚未经过全面测试,可能有点不稳定,使用时需您自担风险:

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
}

The user may be able to pass in any value, but your code can deal with invalid input by passing an error back to them. 用户可能可以传递任何值,但是您的代码可以通过将错误传回给他们来处理无效输入。 If you know the desired format of the incoming data you can directly unmarshal it and handle any invalid input separately. 如果知道所需的传入数据格式,则可以直接将其解组并分别处理任何无效的输入。 This removes the need to have the intermediate interface{} that's hard to deal with: 这样就不需要具有难以处理的中间接口{}:

https://play.golang.org/p/VNCflbk3GL 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}
}

If it's valid for Value to be multiple types, then you will have to do a type assertion somewhere. 如果Value是多种类型有效,那么您将必须在某个地方进行类型声明。 In Go it's not all that painful though: 在Go中,这并没有那么痛苦:

https://newfivefour.com/golang-interface-type-assertions-switch.html 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")
}

You problem is that your incoming data type of Value is map[string]interface{}, and there's no direct/native way in Go to convert map into your type (while there's definitely code out there). 您的问题是Value的传入数据类型是map [string] interface {},而Go中没有直接/本机的方式将map转换为您的类型(尽管那里肯定有代码)。

OK. 好。 If we assume that we totally have no control over incoming data in the Value field, but still, we can identify data type by a combination of its attributes, right? 如果我们假设我们完全无法控制“值”字段中的传入数据,但是仍然可以通过结合其属性来识别数据类型,对吗? Because by definition, you should know possible options. 因为根据定义,您应该知道可能的选择。 We can create a universal incoming object instead of interface{}. 我们可以创建一个通用的传入对​​象而不是interface {}。 AWS is using similar approach in their Go SDK, at least for DynamoDB service, setting optional attributes via pointers: https://github.com/aws/aws-sdk-go/blob/master/service/dynamodb/examples_test.go#L32 AWS至少在DynamoDB服务中正在其Go SDK中使用类似的方法,通过指针设置可选属性: https : //github.com/aws/aws-sdk-go/blob/master/service/dynamodb/examples_test.go# L32

So, the approach is: your UnknownObj struct will have optional attributes that may be filled (and may be not) on json.Unmarshal. 因此,方法是:您的UnknownObj结构将具有可选属性,这些属性可以在json.Unmarshal上填充(也可以不填充)。 Knowing what fields were delivered via the switch, you can guess the data sent. 知道通过交换机传递了哪些字段,您就可以猜测已发送的数据。

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

    }

}

However, if you have control over the root/Values field and you can request to send you specific fields for each of the types instead of pushing all under Values, then you could have: 但是,如果您可以控制root / Values字段,并且可以请求将每种类型的特定字段发送给您,而不是将所有字段都推送到Values下,那么您可以:

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

    ...
}

This way, your solution will be even easier -> you'll just need to check what property is not nil. 这样,您的解决方案将变得更加简单->您只需要检查哪些属性不是nil。

Hope this helps. 希望这可以帮助。

Cheers. 干杯。

Update Dec 15, 2016 To reply on your comment regarding the framework: what you are describing is a process of binding of user's request to an arbitrary data-type. 更新2016年12月15日,以答复您对框架的评论:您所描述的是将用户请求绑定到任意数据类型的过程。 OK. 好。 Unfortunately, its too much code to post here, but here's a link as a starting point: https://github.com/go-playground/validator/blob/v8.18.1/validator.go#L498 不幸的是,它的代码太多,无法在此处发布,但是这里有一个链接作为起点: https : //github.com/go-playground/validator/blob/v8.18.1/validator.go#L498

This is a package and approach Gin framework is using for binding here: https://github.com/gin-gonic/gin/blob/master/binding/json.go 这是Gin框架用于绑定的一种打包方法: https : //github.com/gin-gonic/gin/blob/master/binding/json.go

Good luck! 祝好运!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM