98

I want to convert a struct to map in Golang. It would also be nice if I could use the JSON tags as keys in the created map (otherwise defaulting to field name).

Edit Dec 14, 2020

Since structs repo was archived, you can use mapstructure instead.

Edit TL;DR version, Jun 15, 2015

If you want the fast solution for converting a structure to map, see the accepted answer, upvote it and use that package.

Happy coding! :)


Original Post

So far I have this function, I am using the reflect package but I don't understand well how to use the package, please bear with me.

func ConvertToMap(model interface{}) bson.M {
    ret := bson.M{}

    modelReflect := reflect.ValueOf(model)

    if modelReflect.Kind() == reflect.Ptr {
        modelReflect = modelReflect.Elem()
    }

    modelRefType := modelReflect.Type()
    fieldsCount := modelReflect.NumField()

    var fieldData interface{}

    for i := 0; i < fieldsCount; i++ {
        field := modelReflect.Field(i)

        switch field.Kind() {
        case reflect.Struct:
            fallthrough
        case reflect.Ptr:
            fieldData = ConvertToMap(field.Interface())
        default:
            fieldData = field.Interface()
        }

        ret[modelRefType.Field(i).Name] = fieldData
    }

    return ret
}

Also I looked at JSON package source code, because it should contain my needed implementation (or parts of it) but don't understand too much.

Carson
  • 6,105
  • 2
  • 37
  • 45
eAbi
  • 3,220
  • 4
  • 25
  • 39
  • 1
    Is there a particular goal you're trying to achieve here? If you are dealing with the `mgo/bson` package (which seems possible due to the use of `bson.M`), can't it already perform a conversion from a struct similar to `encoding/json`? – James Henstridge May 11 '14 at 06:33
  • @JamesHenstridge yes it already converts structure to bson representation. Also I can use bson.marshall(from struct)/unmarshall (to map) to perform the conversion. But wanted to make a function converting the struct to map directly. – eAbi May 11 '14 at 06:37
  • 2
    It involves reflection, package [`reflect`](http://golang.org/pkg/reflect), and it is both slow and a royal pain to use; the `json` package is that way because using reflection is hard. My advice would be either to use something that already does the reflection parts for you (object-to-DB interfaces like `gorp` or `mgo`, builtin packages like `json`) or use (possibly repetitive) handwritten code to avoid reflection entirely. It's a situation where the approach that's natural and efficient in, say, JavaScript just isn't in Go. – twotwotwo May 11 '14 at 06:44
  • @twotwotwo Ok, well I already use struct -> bson encoded -> bson decode to map. I think you gave a good lesson in understanding better what Go is. I'm coming from dynamic languages (php and js) and I'm playing with Go at the moment. I was amazed by the language and still am, but sometimes I have this feeling that I'm alone when trying to do something. Thanks! – eAbi May 11 '14 at 06:50
  • https://gist.github.com/tonyhb/5819315 is code someone else wrote to convert simple structs to maps. Again, using `reflect` is wading into a deep pool, so it might be a reasonable alternative to use the `bson` hack or hand-written code per struct, or to somehow avoid the conversion to maps if possible. – twotwotwo May 11 '14 at 07:23
  • 4
    https://github.com/mitchellh/mapstructure - but listen to James. In this case there is no reason to try and do this. – elithrar May 11 '14 at 09:22
  • @elithrar But doesn't mapstructure from github do the opposite of what I want to achieve? `Go library for decoding generic map values into native Go structures` – eAbi May 12 '14 at 09:38
  • 3
    @eAbi First line of the README: "mapstructure is a Go library for decoding generic map values to structures and **vice versa**." (emphasis my own) – elithrar May 12 '14 at 09:41
  • 1
    @elithrar Yes I saw that. I'm sorry I looked several times on that library but didn't saw that `vice versa`. I looked at it on `godoc` but couldn't see how to convert from struct to map. Anyway, thanks. – eAbi May 12 '14 at 09:49

7 Answers7

168

I also had need for something like this. I was using an internal package which was converting a struct to a map. I decided to open source it with other struct based high level functions. Have a look:

https://github.com/fatih/structs

It has support for:

  • Convert struct to a map
  • Extract the fields of a struct to a []string
  • Extract the values of a struct to a []values
  • Check if a struct is initialized or not
  • Check if a passed interface is a struct or a pointer to struct

You can see some examples here: http://godoc.org/github.com/fatih/structs#pkg-examples For example converting a struct to a map is a simple:

type Server struct {
    Name    string
    ID      int32
    Enabled bool
}

s := &Server{
    Name:    "gopher",
    ID:      123456,
    Enabled: true,
}

// => {"Name":"gopher", "ID":123456, "Enabled":true}
m := structs.Map(s)

The structs package has support for anonymous (embedded) fields and nested structs. The package provides to filter certain fields via field tags.

Fatih Arslan
  • 16,499
  • 9
  • 54
  • 55
  • The package you provided here is comprehensive and helpful, thank you! – eAbi Aug 04 '14 at 13:36
  • 6
    wow @Fatih Arslan, this is huge. This practically provides "marshalling" structs to `map[string]interface{}`s just like `encoding/json.Marhsal()`, using struct tags and all. Amazing! – Ory Band Apr 01 '15 at 16:00
  • I used this package but I had my struct fields defined with their json/bson field names such as `bson:"_id" json:"_id,omitempty"` and I want those in the final map and not the exported field names used within Go. Is there a solution? – Gaurav Ojha Sep 12 '16 at 07:57
  • 3
    Here is a way to use "json" tag name instead of the "structs" tag name. https://github.com/fatih/structs/issues/25 – MRHwick Oct 23 '17 at 18:07
  • 43
    Just a note that this project is now archived and has no maintenance. – rfay Nov 27 '18 at 15:14
  • 2
    And I do not want to add another dependency in my project – SouvikMaji Feb 20 '19 at 06:57
  • 1
    Don't forget to export the fields on your struct (Made a noob mistake) – Guy May 14 '19 at 20:18
  • 1
    Any other package that is seen as the replacement? Due to this one being archived? – andres Jul 15 '22 at 15:36
  • @andres Try https://github.com/mitchellh/mapstructure – Ayala Nov 07 '22 at 13:19
  • @Ayala mapstructure decode maps to structs. – Artem Klevtsov Feb 21 '23 at 04:54
  • @ArtemKlevtsov mapstructure is decoding generic map values to structures *and vice versa* – Ayala Feb 26 '23 at 08:20
88

From struct to map[string]interface{}

package main

import (
    "fmt"
    "encoding/json"
)

type MyData struct {
    One   int
    Two   string
    Three int
}

func main() {   
    in := &MyData{One: 1, Two: "second"}

    var inInterface map[string]interface{}
    inrec, _ := json.Marshal(in)
    json.Unmarshal(inrec, &inInterface)

    // iterate through inrecs
    for field, val := range inInterface {
            fmt.Println("KV Pair: ", field, val)
    }
}

go playground here

Zangetsu
  • 1,900
  • 17
  • 25
  • 5
    To keep it more simple, instead of using `var inInterface interface{}`, change it to `var inInterface map[string]interface{}`. See playground here: https://play.golang.org/p/woUiMzL_1X – alextanhongpin Oct 07 '17 at 17:07
  • Clean and simple! Thank you very much! – workdreamer May 24 '18 at 11:19
  • 5
    This looks like a hack, I can't find anything clean or simple about this – John White Jan 28 '19 at 22:59
  • 3
    There is a problem with this approach. In the resulting map, the int values were converted to float64, that is unexpected behavior. The inInterface["One"] value will be of type float64 instead of int. – Bruno Negrão Zica Apr 18 '19 at 20:16
  • 6
    Marshalling and then unmarshalling seems like a very inefficient way of converting data. – d4nyll May 10 '20 at 22:38
  • 2
    @d4nyll do you have another solution? Without using reflection (directly) of course. – Fred Hors Jul 08 '20 at 21:08
  • It works, but it's not an optimal solution, double encoding is executed, shouldn't be used when performance is critical. About 10 times slower than creating a map and adding key/value manually. – Tigran Babajanyan Oct 04 '22 at 15:30
24

Here is a function I've written in the past to convert a struct to a map, using tags as keys

// ToMap converts a struct to a map using the struct's tags.
//
// ToMap uses tags on struct fields to decide which fields to add to the
// returned map.
func ToMap(in interface{}, tag string) (map[string]interface{}, error){
    out := make(map[string]interface{})

    v := reflect.ValueOf(in)
    if v.Kind() == reflect.Ptr {
        v = v.Elem()
    }

    // we only accept structs
    if v.Kind() != reflect.Struct {
        return nil, fmt.Errorf("ToMap only accepts structs; got %T", v)
    }

    typ := v.Type()
    for i := 0; i < v.NumField(); i++ {
        // gets us a StructField
        fi := typ.Field(i)
        if tagv := fi.Tag.Get(tag); tagv != "" {
            // set key of map to value in struct field
            out[tagv] = v.Field(i).Interface()
        }
    }
    return out, nil
}

Runnable example here.

Note, if you have multiple fields with the same tag value, then you will obviously not be able to store them all within a map. It might be prudent to return an error if that happens.

Richard
  • 10,122
  • 10
  • 42
  • 61
Edwardr
  • 2,906
  • 3
  • 27
  • 30
  • Hmm, but I think it doesn't work with nested structs, right? I think the struct fields must be traversed recursively. – eAbi May 12 '14 at 09:52
  • Your question didn't mention that. So you want a struct flattened into a map then? – Edwardr May 12 '14 at 11:14
  • 1
    Yes, a struct flattened into a map (so sub-structures would become a sub-map). You can achieve this by calling the funtion again if the field contains a structure, right? – eAbi May 12 '14 at 11:22
13

I like the importable package for the accepted answer, but it does not translate my json aliases. Most of my projects have a helper function/class that I import.

Here is a function that solves my specific problem.


// Converts a struct to a map while maintaining the json alias as keys
func StructToMap(obj interface{}) (newMap map[string]interface{}, err error) {
    data, err := json.Marshal(obj) // Convert to a json string

    if err != nil {
        return
    }

    err = json.Unmarshal(data, &newMap) // Convert to a map
    return
}

And in the main, this is how it would be called...

package main

import (
    "fmt"
    "encoding/json"
    "github.com/fatih/structs"
)

type MyStructObject struct {
    Email string `json:"email_address"`
}

func main() {
    obj := &MyStructObject{Email: "test@test.com"}

    // My solution
    fmt.Println(StructToMap(obj)) // prints {"email_address": "test@test.com"}

    // The currently accepted solution
    fmt.Println(structs.Map(obj)) // prints {"Email": "test@test.com"}
}
Sebastian Lenartowicz
  • 4,695
  • 4
  • 28
  • 39
Eldy
  • 173
  • 1
  • 5
  • 2
    It works, but it's not an optimal solution, double encoding is executed, shouldn't be used when performance is critical. About 10 times slower than creating a map and adding key/value manually. – Tigran Babajanyan Oct 04 '22 at 15:28
4
package main

import (
    "fmt"
    "reflect"
)

type bill struct {
    N1 int
    N2 string
    n3 string
}

func main() {
    a := bill{4, "dhfthf", "fdgdf"}

    v := reflect.ValueOf(a)

    values := make(map[string]interface{}, v.NumField())

    for i := 0; i < v.NumField(); i++ {
        if v.Field(i).CanInterface() {
            values[v.Type().Field(i).Name] = v.Field(i).Interface()
        } else {
            fmt.Printf("sorry you have a unexported field (lower case) value you are trying to sneak past. I will not allow it: %v\n", v.Type().Field(i).Name)
        }
    }

    fmt.Println(values)

    passObject(&values)
}

func passObject(v1 *map[string]interface{}) {
    fmt.Println("yoyo")
}
derek
  • 61
  • 4
  • this also works for structs coming from different packages. (note you won't have access to unexported fields) – derek Dec 14 '18 at 22:18
1

I'm a bit late but I needed this kind of feature so I wrote this. Can resolve nested structs. By default, uses field names but can also use custom tags. A side effect is that if you set the tagTitle const to json, you could use the json tags you already have.

package main

import (
    "fmt"
    "reflect"
)

func StructToMap(val interface{}) map[string]interface{} {
    //The name of the tag you will use for fields of struct
    const tagTitle = "kelvin"

    var data map[string]interface{} = make(map[string]interface{})
    varType := reflect.TypeOf(val)
    if varType.Kind() != reflect.Struct {
        // Provided value is not an interface, do what you will with that here
        fmt.Println("Not a struct")
        return nil
    }

    value := reflect.ValueOf(val)
    for i := 0; i < varType.NumField(); i++ {
        if !value.Field(i).CanInterface() {
            //Skip unexported fields
            continue
        }
        tag, ok := varType.Field(i).Tag.Lookup(tagTitle)
        var fieldName string
        if ok && len(tag) > 0 {
            fieldName = tag
        } else {
            fieldName = varType.Field(i).Name
        }
        if varType.Field(i).Type.Kind() != reflect.Struct {
            data[fieldName] = value.Field(i).Interface()
        } else {
            data[fieldName] = StructToMap(value.Field(i).Interface())
        }

    }

    return data
}

Dharman
  • 30,962
  • 25
  • 85
  • 135
Kelvin
  • 79
  • 1
  • 7
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Feb 26 '22 at 10:02
-5
map := Structpb.AsMap()

// map is the map[string]interface{}
Aman Agarwal
  • 396
  • 3
  • 5