I have a map of this kind type Store map[string]string
From this map, i would like to fill a struct. The goal is, for each field of the struct, find the key that match the field name, take the value, and based on the type of the struct field, convert the value to the proper struct field type and set it.
Basically the map will contains integers, booleans, strings and durations as strings so the convestion should be simply strconv.atoi()
, time.parseDuration()
...
Also, i would like to use the structs tags to specify the name of the key in the map, because the struct field will be likely camelCase, while the keys in the map will be like this "example_key"
Any idea of how doing this. I have read about golang reflection but it is still opaque for me. I would just need an explanation to approach the problem, and then i think i can handle the implementation by my own.
Thanks
To avoid some extended discussion in comments I'm just going to make an answer explaining the two approaches I would choose from. Generally speaking, I would avoid the reflective method. There are cases where it is called for but most of the time the more verbose, literal, simple code will be better, even if it's a few more lines and there is some overlap between what the methods do.
So, starting with the simple example. Suppose you got 3 types, GroceryStore
, ComputerStore
, and CornerStore
. In whatever package each type is defined in, it is fairly common to define some constructor like methods (they're not technically constructors, just package scoped methods that instantiate a type and return it but same purpose). So an example would be;
func NewComputerStore(store Store) *ComputerStore {
return &ComputerStore{
Name: store["Name"],
Location: store["Location"],
//ect
}
}
Of course the example above is completely unsafe. In reality you need to use the v, ok := myMape["key"]
syntax and then assign if ok
and do whatever if not (probably some combination of assign default value, log error, and throw error).
Now if you want a generalized method... It typically look something more like this (note this is untested and is more pseudo code than a working example);
func (s *Store) ConvertToObject(obj interface{}) error {
val := reflect.ValueOf(obj)
for i := 0; i < val.NumField(); i++ {
switch v := val.Field(i).(type) {
case int64:
val.Field(i).SetInt(strconv.Atoi(s[val.Field(i).Name]))
}
}
}
So the basic idea of the code above is this. You're starting with a store map. The caller knows what type that map is supposed to be. They call this method passing a new instance of that type. In the method we don't really care what the type is, we care what the type of it's fields are. So I iterate all the fields on the object passed in. Each field is put through the type switch (since I'll need a string to X conversion for every type that is present in the structs this method should work with, or at least a decent base case), in the case for each type you can do assignment using the field name to look up it's value in the Store
map and converting from string to X.
Hope that makes sense. I can follow up if it doesn't really clarify the approach. Remember the code above is not intended to run. Also, I've ignored all error handling which you will need lots of to make a generalized function work (what to do if conversion fails? What if type isn't recognized? What about all the other possible errors that won't be common place?).