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
}
Related
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"}
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
This question already has an answer here:
How to unmarshal a field that can be an array or a string in Go?
(1 answer)
Closed 3 years ago.
I have an app where the type is
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
But we have legacy clients that send the Age field as either a string or an integer, so...
{
"name": "Joe",
"age": "42"
}
OR
{
"name": "Joe",
"age": 42
}
I know I can annotate the "age" field with ",string" if it's a string that I want coerced into an integer, but what if the field could be either one?
Check out "json - raw Message" in the docs, from there on out you can try to parse it however you want. Example below and on GoPlayground
package main
import (
"encoding/json"
"fmt"
"log"
"strconv"
"unicode/utf8"
)
type Person struct {
Name string `json:"name"`
Age json.RawMessage `json:"age"`
}
func main() {
var j = []byte(`{"name": "Joe","age": "42"}`)
var j2 = []byte(`{"name": "Joe","age": 42}`)
stringOrInt(j)
stringOrInt(j2)
}
func stringOrInt(bytes []byte) {
var p Person
err := json.Unmarshal(bytes, &p)
if err != nil {
log.Fatal(err)
}
if utf8.Valid(p.Age) {
i, err := strconv.Atoi(string(p.Age))
if err != nil {
fmt.Println("got int " + strconv.Itoa(i))
} else {
fmt.Println("got string")
}
} else {
fmt.Println("whoops")
}
}
package main
import (
"encoding/json"
"fmt"
"strconv"
)
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
func (p *Person) UnmarshalJSON(b []byte) error {
var objMap map[string]*json.RawMessage
err := json.Unmarshal(b, &objMap)
if err != nil {
return err
}
var name string
err = json.Unmarshal(*objMap["name"], &name)
if err != nil {
return err
}
var ageInt int
err = json.Unmarshal(*objMap["age"], &ageInt)
if err != nil {
// age is string
var ageString string
err = json.Unmarshal(*objMap["age"], &ageString)
if err != nil {
return err
}
aI, err := strconv.Atoi(ageString)
if err != nil {
return err
}
p.Age = aI
} else {
p.Age = ageInt
}
p.Name = name
fmt.Printf("%+v", *p)
return nil
}
func main() {
p := `{"name": "John", "age": "10"}`
// p := `{"name": "John", "age": 10}`
newP := Person{}
err := newP.UnmarshalJSON([]byte(p))
if err != nil {
fmt.Printf("Error %+v", err)
}
}
https://play.golang.org/p/AK8H_wdNqmt
You can try something like this. Try to read and parse Into int, if that fails check for string value, parse it to int and then assign int value to person struct
package main
import (
"encoding/json"
"fmt"
)
type Person struct {
First string `json:"name"`
}
type person struct {
Last string `json:"name"`
}
type A struct {
*Person `json:"person"`
}
func (a *A) UnmarshalJSON(b []byte) error {
type alias A
bb := struct {
*person `json:"person"`
*alias
}{
alias: (*alias)(a),
}
if err := json.Unmarshal(b, &bb); err != nil {
return err
}
fmt.Printf("%+v\n", bb.person)
return nil
}
func main() {
b := []byte(`{"person": {"name": "bob"}}`)
a := &A{}
if err := json.Unmarshal(b, a); err != nil {
fmt.Println(err)
}
fmt.Printf("%+v\n", a.Person)
}
results in:
&{Last:bob}
<nil>
Why does bb.person and a.Person have the same struct tag but only bb.person gets filled in? I haven't been able to find the appropriate documentation but why does this happen and is it guaranteed to always happen?
https://play.golang.org/p/Fvo_hg3U6r
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