I've got a Set function that wraps a users object (or variable) in my own struct called sessions. 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.
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.
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.
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.
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
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. In Go it's not all that painful though:
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).
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{}. 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
So, the approach is: your UnknownObj struct will have optional attributes that may be filled (and may be not) on 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:
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.
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. 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
This is a package and approach Gin framework is using for binding here: https://github.com/gin-gonic/gin/blob/master/binding/json.go
Good luck!
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. 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
}