Converting map to struct - go

I am trying to create a generic method in Go that will fill a struct using data from a map[string]interface{}. For example, the method signature and usage might look like:
func FillStruct(data map[string]interface{}, result interface{}) {
...
}
type MyStruct struct {
Name string
Age int64
}
myData := make(map[string]interface{})
myData["Name"] = "Tony"
myData["Age"] = 23
result := &MyStruct{}
FillStruct(myData, result)
// result now has Name set to "Tony" and Age set to 23
I know this can be done using JSON as an intermediary; is there another more efficient way of doing this?

The simplest way would be to use https://github.com/mitchellh/mapstructure
import "github.com/mitchellh/mapstructure"
mapstructure.Decode(myData, &result)
If you want to do it yourself, you could do something like this:
http://play.golang.org/p/tN8mxT_V9h
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 structFieldType != val.Type() {
return errors.New("Provided value type didn't match obj field type")
}
structFieldValue.Set(val)
return nil
}
type MyStruct struct {
Name string
Age int64
}
func (s *MyStruct) FillStruct(m map[string]interface{}) error {
for k, v := range m {
err := SetField(s, k, v)
if err != nil {
return err
}
}
return nil
}
func main() {
myData := make(map[string]interface{})
myData["Name"] = "Tony"
myData["Age"] = int64(23)
result := &MyStruct{}
err := result.FillStruct(myData)
if err != nil {
fmt.Println(err)
}
fmt.Println(result)
}

Hashicorp's https://github.com/mitchellh/mapstructure library does this out of the box:
import "github.com/mitchellh/mapstructure"
mapstructure.Decode(myData, &result)
The second result parameter has to be an address of the struct.

the simplest way to do that is using encoding/json package
just for example:
package main
import (
"fmt"
"encoding/json"
)
type MyAddress struct {
House string
School string
}
type Student struct {
Id int64
Name string
Scores float32
Address MyAddress
Labels []string
}
func Test() {
dict := make(map[string]interface{})
dict["id"] = 201902181425 // int
dict["name"] = "jackytse" // string
dict["scores"] = 123.456 // float
dict["address"] = map[string]string{"house":"my house", "school":"my school"} // map
dict["labels"] = []string{"aries", "warmhearted", "frank"} // slice
jsonbody, err := json.Marshal(dict)
if err != nil {
// do error check
fmt.Println(err)
return
}
student := Student{}
if err := json.Unmarshal(jsonbody, &student); err != nil {
// do error check
fmt.Println(err)
return
}
fmt.Printf("%#v\n", student)
}
func main() {
Test()
}

You can do it ... it may get a bit ugly and you'll be faced with some trial and error in terms of mapping types .. but heres the basic gist of it:
func FillStruct(data map[string]interface{}, result interface{}) {
t := reflect.ValueOf(result).Elem()
for k, v := range data {
val := t.FieldByName(k)
val.Set(reflect.ValueOf(v))
}
}
Working sample: http://play.golang.org/p/PYHz63sbvL

There are two steps:
Convert interface to JSON Byte
Convert JSON Byte to struct
Below is an example:
dbByte, _ := json.Marshal(dbContent)
_ = json.Unmarshal(dbByte, &MyStruct)

You can roundtrip it through JSON:
package main
import (
"bytes"
"encoding/json"
)
func transcode(in, out interface{}) {
buf := new(bytes.Buffer)
json.NewEncoder(buf).Encode(in)
json.NewDecoder(buf).Decode(out)
}
Example:
package main
import "fmt"
type myStruct struct {
Name string
Age int64
}
func main() {
myData := map[string]interface{}{
"Name": "Tony",
"Age": 23,
}
var result myStruct
transcode(myData, &result)
fmt.Printf("%+v\n", result) // {Name:Tony Age:23}
}

I adapt dave's answer, and add a recursive feature. I'm still working on a more user friendly version. For example, a number string in the map should be able to be converted to int in the struct.
package main
import (
"fmt"
"reflect"
)
func SetField(obj interface{}, name string, value interface{}) error {
structValue := reflect.ValueOf(obj).Elem()
fieldVal := structValue.FieldByName(name)
if !fieldVal.IsValid() {
return fmt.Errorf("No such field: %s in obj", name)
}
if !fieldVal.CanSet() {
return fmt.Errorf("Cannot set %s field value", name)
}
val := reflect.ValueOf(value)
if fieldVal.Type() != val.Type() {
if m,ok := value.(map[string]interface{}); ok {
// if field value is struct
if fieldVal.Kind() == reflect.Struct {
return FillStruct(m, fieldVal.Addr().Interface())
}
// if field value is a pointer to struct
if fieldVal.Kind()==reflect.Ptr && fieldVal.Type().Elem().Kind() == reflect.Struct {
if fieldVal.IsNil() {
fieldVal.Set(reflect.New(fieldVal.Type().Elem()))
}
// fmt.Printf("recursive: %v %v\n", m,fieldVal.Interface())
return FillStruct(m, fieldVal.Interface())
}
}
return fmt.Errorf("Provided value type didn't match obj field type")
}
fieldVal.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
}
type OtherStruct struct {
Name string
Age int64
}
type MyStruct struct {
Name string
Age int64
OtherStruct *OtherStruct
}
func main() {
myData := make(map[string]interface{})
myData["Name"] = "Tony"
myData["Age"] = int64(23)
OtherStruct := make(map[string]interface{})
myData["OtherStruct"] = OtherStruct
OtherStruct["Name"] = "roxma"
OtherStruct["Age"] = int64(23)
result := &MyStruct{}
err := FillStruct(myData,result)
fmt.Println(err)
fmt.Printf("%v %v\n",result,result.OtherStruct)
}

Here function to convert map to struct by tag. If tag not exist it will find by fieldByName.
Thanks to https://gist.github.com/lelandbatey/a5c957b537bed39d1d6fb202c3b8de06
type MyStruct struct {
Name string `json:"name"`
ID int `json:"id"`
}
myStruct := &MyStruct{}
for k, v := range mapToConvert {
err := MapToStruct(myStruct, k, v)
if err != nil {
fmt.Println(err)
}
}
func MapToStruct(s interface{}, k string, v interface{}) error {
var jname string
structValue := reflect.ValueOf(s).Elem()
fieldByTagName := func(t reflect.StructTag) (string, error) {
if jt, ok := t.Lookup("keyname"); ok {
return strings.Split(jt, ",")[0], nil
}
return "", fmt.Errorf("tag provided %s does not define a json tag", k)
}
fieldNames := map[string]int{}
for i := 0; i < structValue.NumField(); i++ {
typeField := structValue.Type().Field(i)
tag := typeField.Tag
if string(tag) == "" {
jname = toMapCase(typeField.Name)
} else {
jname, _ = fieldByTagName(tag)
}
fieldNames[jname] = i
}
fieldNum, ok := fieldNames[k]
if !ok {
return fmt.Errorf("field %s does not exist within the provided item", k)
}
fieldVal := structValue.Field(fieldNum)
fieldVal.Set(reflect.ValueOf(v))
return nil
}
func toMapCase(s string) (str string) {
runes := []rune(s)
for j := 0; j < len(runes); j++ {
if unicode.IsUpper(runes[j]) == true {
if j == 0 {
str += strings.ToLower(string(runes[j]))
} else {
str += "_" + strings.ToLower(string(runes[j]))
}
} else {
str += strings.ToLower(string(runes[j]))
}
}
return str
}

Simple way just marshal it json string
and then unmarshat it to struct
here is the link

Related

Is there an easy way to create a struct in golang?

I have a struct and now want to instantiate it from received http data. But now the code I write is cumbersome and has a lot of lines of code. Is there any way to simplify the code? All fields except the field id can correspond
model
type ALiNotifyLog struct {
ID *int `json:"id"`
APPId *string `json:"app_id"`
AuthAppId *string `json:"auth_app_id"`
BuyerId *string `json:"buyer_id"`
BuyerPayAmount *string `json:"buyer_pay_amount"`
GmtCreate *string `json:"gmt_create"`
GmtPayment *string `json:"gmt_payment"`
InvoiceAmount *string `json:"invoice_amount"`
NotifyId *string `json:"notify_id"`
NotifyTime *string `json:"notify_time"`
OutTradeNo *string `json:"out_trade_no"`
PointAmount *string `json:"point_amount"`
ReceiptAmount *string `json:"receipt_amount"`
Sign *string `json:"sign"`
TotalAmount *string `json:"total_amount"`
TradeNo *string `json:"trade_no"`
TradeStatus *string `json:"trade_status"`
}
func
func SaveData(data map[string]interface{}) {
app_id := data["app_id"].(string)
auth_app_id := data["auth_app_id"].(string)
buyer_id := data["buyer_id"].(string)
buyer_pay_amount := data["buyer_pay_amount"].(string)
gmt_create := data["gmt_create"].(string)
gmt_payment := data["gmt_payment"].(string)
invoice_amount := data["invoice_amount"].(string)
notify_id := data["notify_id"].(string)
notify_time := data["notify_time"].(string)
out_trade_no := data["out_trade_no"].(string)
point_amount := data["point_amount"].(string)
receipt_amount := data["receipt_amount"].(string)
sign := data["sign"].(string)
total_amount := data["total_amount"].(string)
trade_no := data["trade_no"].(string)
trade_status := data["trade_status"].(string)
model := payment.ALiNotifyLog{
APPId: &app_id,
AuthAppId: &auth_app_id,
BuyerId: &buyer_id,
BuyerPayAmount: &buyer_pay_amount,
GmtCreate: &gmt_create,
GmtPayment: &gmt_payment,
InvoiceAmount: &invoice_amount,
NotifyId: &notify_id,
NotifyTime: &notify_time,
OutTradeNo: &out_trade_no,
PointAmount: &point_amount,
ReceiptAmount: &receipt_amount,
Sign: &sign,
TotalAmount: &total_amount,
TradeNo: &trade_no,
TradeStatus: &trade_status}
res := global.Orm.Table(paynotifylog).Create(&model)
fmt.Println(res)
}
I see that you are JSON. May be decode the JSON directly into a struct instance.
I will structure the code something like the below snippet:
type ALiNotifyLog struct {
// your fields here
}
func parseRequest(r *http.Request) {
var notifyLog ALiNotifyLog
err := json.NewDecoder(r.Body).Decode(&notifyLog)
if err != nil {
// do something
}
// ............ more code
}
func SaveData(data ALiNotifyLog) {
res := global.Orm.Table(paynotifylog).Create(&data)
fmt.Println(res)
// ........... more code
}
Use the reflect package to programmatically iterate over the fields:
func setStringpFields(pmodel any, data map[string]any) {
v := reflect.ValueOf(pmodel).Elem()
t := v.Type()
for i := 0; i < t.NumField(); i++ {
sf := t.Field(i)
if sf.Type != stringpType {
continue
}
name, _, _ := strings.Cut(sf.Tag.Get("json"), ",")
if s, ok := data[name].(string); ok {
v.Field(i).Set(reflect.ValueOf(&s))
}
}
}
var stringpType = reflect.PtrTo(reflect.TypeOf(""))
Use it like this:
var model ALiNotifyLog
setStringpFields(&model, data)
Run an example on the Go Playground.
I took the liberty of skipping fields that are missing from data. The code in the question panics on a missing value.
A simpler approach is to create a function with the repeated functionality:
func stringp(data map[string]interface{}, name string) *string {
if s, ok := data[name].(string); ok {
return &s
}
return nil
}
Use that function to initialize the fields:
model := payment.ALiNotifyLog{
APPId: stringp("app_id"),
AuthAppId: stringp("auth_app_id"),
...
TradeStatus: stringp("trade_status")}
res := global.Orm.Table(paynotifylog).Create(&model)
fmt.Println(res)
1: ScanMapToStruct
func scanMapToStruct(dest interface{}, vals map[string]interface{}) error {
srcValue := indirect(reflect.ValueOf(dest))
srcType := indirectType(reflect.TypeOf(dest))
for m := 0; m < srcType.NumField(); m++ {
field := srcType.Field(m)
fieldName, _ := getFieldName(field)
jsonTypeName := getJsonDataType(field.Type)
if jsonTypeName == "" {
continue
}
v, ok := vals[fieldName].(string)
if !ok {
continue
}
dec := decoders[field.Type.Kind()]
if dec == nil {
continue
}
err := dec(srcValue.Field(m), v)
if err != nil {
fmt.Printf("set field(%s)=%s err:%v\n", fieldName, v, err)
continue
}
}
return nil
}
2: copier.Copy()
Use package mapstructure from GitHub.
go get https://github.com/mitchellh/mapstructure
package main
import (
"log"
"os"
"github.com/mitchellh/mapstructure"
)
type MyStruct struct {
This int
That string `json:"thaaaaat"`
}
func main() {
var result map[string]interface{}
cfg := &mapstructure.DecoderConfig{
TagName: "json",
Result: &result,
}
decoder, err := mapstructure.NewDecoder(cfg)
if err != nil {
log.Printf("Could not create decoder: %v", err)
os.Exit(1)
}
myData := &MyStruct{
This: 42,
That: "foobar",
}
err = decoder.Decode(myData)
if err != nil {
log.Printf("Decoding failed: %v", err)
os.Exit(1)
}
log.Print(cfg.Result)
}
Output:
&map[This:42 thaaaaat:foobar]
https://go.dev/play/p/mPK_9fEevyC

String to float64 receiving format ".01"

If I receive from an API a string obeying the format of ".01", and I have a struct like this:
type Mystruct struct {
Val float64 json:"val,string"
}
In this case, I receive trying to unmarshal val into float64. Is there a way I can accomplish this?
Add a string field to capture the string value:
type Mystruct struct {
Val float64 `json:"-"`
XVal string `json:"val"`
}
Unmarshal the JSON document. Convert the string value to a float value:
var v Mystruct
err := json.Unmarshal([]byte(data), &v)
if err != nil {
log.Fatal(err)
}
v.Val, err = strconv.ParseFloat(v.XVal, 64)
if err != nil {
log.Fatal(err)
}
I recommand defining a type alias which you can use it anywhere.
package main
import (
"encoding/json"
"fmt"
"strconv"
"strings"
)
type MyFloat64 float64
func (f *MyFloat64) UnmarshalJSON(data []byte) error {
raw := string(data)
raw = strings.TrimPrefix(raw, "\"")
raw = strings.TrimSuffix(raw, "\"")
if parsedFloat, err := strconv.ParseFloat(raw, 64); err != nil {
return err
} else {
*f = MyFloat64(parsedFloat)
return nil
}
}
type MyObj struct {
Val1 MyFloat64
Val2 string
}
func main() {
j := `{"Val1":"0.01", "Val2":"0.01"}`
o := MyObj{}
err := json.Unmarshal([]byte(j), &o)
if err != nil {
fmt.Println(err)
} else {
b, _ := json.Marshal(o)
fmt.Println("in:", j)
fmt.Println("out:", string(b))
}
}
output:
in: {"Val1":"0.01", "Val2":"0.01"}
out: {"Val1":0.01,"Val2":"0.01"}

How to access map[string]interface {}?

How can i programmatically access the msg value "Design" in the go language structure shown below?
after subIssues[28].Fields.Unknowns["customfield_11801"] i dont find a language construct to access the data structure.
To convert into a struct and work with that, have a look at this repo: https://github.com/mitchellh/mapstructure.
And if you want to do it your self, something like this: (Adjust implementation to match your project)
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 structFieldType != val.Type() {
return errors.New("Provided value type didn't match obj field type")
}
structFieldValue.Set(val)
return nil
}
type MyStruct struct {
Name string
Age int64
}
func (s *MyStruct) FillStruct(m map[string]interface{}) error {
for k, v := range m {
err := SetField(s, k, v)
if err != nil {
return err
}
}
return nil
}
func main() {
myData := make(map[string]interface{})
myData["Name"] = "Tony"
myData["Age"] = int64(23)
result := &MyStruct{}
err := result.FillStruct(myData)
if err != nil {
fmt.Println(err)
}
fmt.Println(result)
}

Transform struct to slice struct

I'm trying to select a struct by string input and then depending on the return JSON Object or Array, unmarshall the JSON. Is it correct to think of a way to reflect the struct to slice struct? if so how to do that with reflection?
Regards,
Peter
package main
import (
"bytes"
"encoding/json"
"fmt"
"log"
)
type NameStruct struct {
Name string
}
func main() {
jsonData := []byte(`[{"name":"james"},{"name":"steven"}]`)
returnModel := InitializeModel("NameStruct", jsonData)
fmt.Println(returnModel)
jsonData = []byte(`{"name":"james"}`)
returnModel = InitializeModel("NameStruct", jsonData)
fmt.Println(returnModel)
}
func getModelByName(modelType string) interface{} {
modelMap := make(map[string]interface{})
modelMap["NameStruct"] = new(NameStruct)
//don't want to do this
modelMap["arrNameStruct"] = new([]NameStruct)
return modelMap[modelType]
}
func InitializeModel(modelName string, jsonData []byte) interface{} {
switch IsArray(jsonData) {
case true:
// some conversion here, how?
returnModel := getModelByName("NameStruct")
if err := json.Unmarshal(jsonData, &returnModel); err != nil {
log.Println(err)
}
return returnModel
case false:
returnModel := getModelByName("NameStruct")
if err := json.Unmarshal(jsonData, &returnModel); err != nil {
log.Println(err)
}
return returnModel
}
return nil
}
func IsArray(jsonData []byte) bool {
return (bytes.HasPrefix(jsonData, []byte("["))) && (bytes.HasSuffix(jsonData, []byte("]")))
}
Expanding on my comment, you can create a Factory where pre-defined types are registered:
type Factory struct {
m map[string]reflect.Type
}
func (f *Factory) Register(v interface{}) {
vt := reflect.TypeOf(v)
n := vt.Name()
f.m[n] = vt
f.m["[]"+n] = reflect.SliceOf(vt) // implicitly register a slice of type too
}
these types can be looked up by name at runtime and initialized with JSON data:
func (f *Factory) Make(k string, bs []byte) (interface{}, error) {
vt, ok := f.m[k]
if !ok {
return nil, fmt.Errorf("type %q not registered", k)
}
pv := reflect.New(vt).Interface()
err := json.Unmarshal(bs, pv)
if err != nil {
return nil, err
}
return pv, nil
}
To use:
type Place struct {
City string `json:"city"`
}
factory.Register(Place{})
p, err := factory.Make("Place", []byte(`{"city":"NYC"}`))
fmt.Printf("%#v\n", p) // &main.Place{City:"NYC"}
Slices also work:
ps, err := factory.Make("[]Place", []byte(`[{"city":"NYC"},{"city":"Dublin"}]`))
fmt.Printf("%#v\n", p, p) // &[]main.Place{main.Place{City:"NYC"}, main.Place{City:"Dublin"}}
Playground: https://play.golang.org/p/qWEdwk-YUug

Access Struct when struct is in an Slice

I need to generate a Form from the struct below. I need to have access to every field and type on the Doc to generate the Form. My problem is to access the struct fields when they are on an slice.
This is the code
package main
import (
"fmt"
"net/url"
"time"
"reflect"
"strings"
"strconv"
)
type TestStruct struct {
Children22 struct {// I can acess this one ( see code)
ID string
Name string
}
Children23 []struct { // this is not struct it is slice of sruct
ID1 string // I would like to access this one
Name1 string
}
Nest struct {
Children []struct { // I would like to get to this one too
ID2 string
Name2 string
}
}
}
func main() {
var t1 TestStruct
t1.InterfaceStruct = &InterfaceStruct{}
main := reflect.ValueOf(&t1)
err := GetField(main)
if err != nil {
fmt.Println("Error lev 1 = ", err)
}
}
func GetField ( f reflect.Value ) error {
if f.Kind() != reflect.Ptr {
fmt.Println("Error not a pointer ")
}
val := f.Elem()
lenght := val.NumField()
for i := 0; i < lenght; i++ {
typefield := val.Type().Field(i)
elementType := val.Type().Field(i).Type
type_filed := val.Type().Field(i).Type.String()
elemKind := elementType.Kind()
fmt.Println("typefield.Name = ", typefield.Name)
fmt.Println("elementType = ", elementType)
fmt.Println("what kind =", elemKind )
if elemKind == reflect.Slice{
fmt.Println("is a Slice")
fmt.Println(" Slice type =", val.Field(i).Addr().Elem().Type() )
// what kind of slice
if strings.Contains(type_filed ,"struct") {
// I do not know how to address the struct inside the slice
}
}
if elemKind == reflect.Struct { // check first for time and URL are Struct
if type_filed != "time.Time" && type_filed != "url.URL" {
fmt.Println("is a struct ")
//pass this to function recursive as reflect.Value
newSt := val.Field(i).Addr() // this works fine
err := GetField(newSt) // recall the func to get the struct
if err != nil {
fmt.Println("Error = ", err)
}
}
}
if elemKind == reflect.Map {
fmt.Println("is a Map")
}
fmt.Println(" ")
}
return nil
}

Resources