Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Convert map to struct

Tags:

go

Ok, the title is a little bit misleading. What I'm after is as follows:

type MyStruct struct {
    id   int
    name string
    age  int
}

func CreateFromMap(m map[string]interface{}) (MyStruct, error) {
    var (
        id   int
        name string
        age  int
        ok   bool
    )
    err := errors.New("Error!")
    id, ok = m["id"].(int)
    if !ok {
        return nil, err
    }
    name, ok = m["name"].(string)
    if !ok {
        return nil, err
    }
    age, ok = m["age"].(int)
    if !ok {
        return nil, err
    }
    return MyStruct{id, name, age}, nil
}

Don't ask: Why I'm not using CreateFromMap(int, string, int). That object comes from somewhere else, out of my control.

It's already boring to map each key, value pair in the map to struct properties. But checking if everything is ok or not after each conversion is chaotic.

Is there an easier way of doing this other than reflection?

like image 634
mostruash Avatar asked Nov 26 '15 21:11

mostruash


2 Answers

You can just Marshal/Unmarshal, but property names should match

func CreateFromMap(m map[string]interface{}) (MyStruct, error) {
    data, _ := json.Marshal(m)
    var result MyStruct
    err := json.Unmarshal(data, &result)
    return result, err
}
like image 42
Georgi Alexandrov Avatar answered Oct 11 '22 20:10

Georgi Alexandrov


Let's say I assume you don't want to use reflection because you don't want to do it yourself. In this case, what about using an external package that does it for you ?

package main

import "fmt"
import "github.com/mitchellh/mapstructure"

type MyStruct struct {
    Id   int
    Name string
    Age  int
}

func main() {
    var m = make(map[string]interface{})
    m["Id"] = 17
    m["Name"] = "foo"
    m["Age"] = 42
    fmt.Printf("%+v\n", m)

    res, err := CreateFromMap(m)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Printf("%+v\n", res)
}

func CreateFromMap(m map[string]interface{}) (MyStruct, error) {
    var result MyStruct
    err := mapstructure.Decode(m, &result)
    return result, err
}

Output:

map[Age:42 Name:foo Id:17]
{Id:17 Name:foo Age:42}

It has the advantage to work whatever your structure looks like, but it uses reflection internally. Actually, I don't see any "nice" way to do what you want without using reflection and/or repetitive code for each attribute of your structure. The downside, however, is that you will have to use capitalized attributes so that they would be exported to external packages.

Edit (to answer your question in the comments):

On my opinion, if you want to specify additional rules when "creating" the structure, it should be after the decoding operation. For instance:

func CreateFromMap(m map[string]interface{}) (MyStruct, error) {
    var result MyStruct
    err := mapstructure.Decode(m, &result)
    if err != nil {
         return result, err
    }
    if result.Age <= 0 {
        result.Age = 0
    }
    if result.Name == "" {
        return result, errors.New("empty name is not allowed")
    }

    return result, err
}

This way, you will clearly separate the "conversion" part from your structure's specific rules processing.

like image 78
julienc Avatar answered Oct 11 '22 21:10

julienc