Golang Reflection: Inspect a struct type definition to extract its properties without initialization - go

If I have the following declaration
type Foo struct {
bar string
}
Can I use reflection to inspect the properties on the declaration without initialising it?
keys := reflect.something(Foo)
for _, key := range keys {
fmt.Println(key) // "bar"
}

Use reflect.TypeOf to get the reflect.Type for Foo.
t := reflect.TypeOf(Foo{})
If you don't want to create a value of the type, then get the pointer type and "dereference" that type.
t := reflect.TypeOf((*Foo)(nil)).Elem()
The expression (*Foo)(nil) returns a nil pointer to the type. The Type.Elem method returns the pointed to type.
Iterate through the fields on the type. Type.NumField returns the number of fields on the type. Type.Field returns a StructField by field index.
for i := 0; i < t.NumField; i++ {
fmt.Println(t.Field(i).Name)
}
Run it on the playground.

Related

"non-interface type map[string]interface {} on left" error

I have a struct:
type ListsObj struct {
Page string `json:"pages"`
Count string `json:"count"`
Lists []map[string]interface{} `json:"assets"`
}
I am trying to do something like below:
lists := a.Lists
for _, list:= range lists{
listData := list.(map[string]interface {})
}
a is of type ListsObj struct.
I am getting following error:
invalid type assertion: list.(map[string]) (non-interface type
map[string]interface {} on left)
EDIT:
What I actually need to do is to call a function:
func WriteMapper(a interface {}) interface{} {
}
lists := a.Lists
for _, list:= range lists{
list = WriteMapper(list)
}
but this gives another error:
cannot use WriteMapper(list) (type interface {}) as type
map[string]interface {} in assignment: needs type assertion
EDIT: I think I got it... the function returns interface and I am trying to assign that to map[string]interface {}??
In your code, a.Lists (and therefore also lists) is a []map[string]interface{}, a slice of maps. You're then iterating over that slice, and assigning the value on each iteration to the variable list (which is kinda oddly named, as it's holding a map, but that's a separate concern).
Now, list is of type map[string]interface{}. There's no need for you to type-assert it into that, since it's already that type!
To use list as the map[string]interface{} that it is, you just use it:
for _, list := range lists {
someValue := list["some-key"]
fmt.Println(someValue)
}
If you're trying to do something different that just using the map, I'd ask that you please clarify.

How to use reflect to set every field of struct to non-nil value in go

Suppose I have some type, and I want to instantiate a variable of this type, with every value non-nil.
type Event struct {
HappenedAtMs int64
ReceivedAtMs int64
FieldA *FieldAType
FieldB []*FieldBType
Here is what I am currently trying:
eventFields := reflect.TypeOf(Event{})
event := Event{}
for i := 0; i < eventFields.NumField(); i++ {
nonEmptyType := reflect.New(eventFields.Field(i).Type).Elem()
reflect.ValueOf(&event).Elem().Field(i).Set(nonEmptyType)
}
However, upon running this code, all the fields in the event variable are still set to nil. How can I achieve what I want?
The reflect package needs a pointer to the struct for it to be able to set its fields. The fields also need to be exported which you can check against using the CanSet method.
To initialize a pointer type with reflect you can simply do reflect.New(T.Elem()). To initialize a map, a slice, a func, or a chan type to non-nil you can use the MakeMap, MakeSlice, MakeFunc, and MakeChan functions respectively. To initialize an interface type to non-nil you can create an anonymous struct type, using reflect.StructOf, with a single embedded field of the target interface type, by embedding the interface type the struct type automatically satisfies the interface and an instance of it can be used to set the field to non-nil.
event := Event{}
rv := reflect.ValueOf(&event).Elem()
for i := 0; i < rv.NumField(); i++ {
if f := rv.Field(i); isNilable(f) && f.IsNil() && f.CanSet() {
switch f.Kind() {
case reflect.Ptr:
f.Set(reflect.New(f.Type().Elem()))
case reflect.Slice:
f.Set(reflect.MakeSlice(f.Type(), 0, 0))
case reflect.Interface:
sf := reflect.StructField{
Name: f.Type().Name(),
Type: f.Type(),
Anonymous: true,
}
rt := reflect.StructOf([]reflect.StructField{sf})
f.Set(reflect.New(rt).Elem())
// TODO handle the rest of nilable types
}
}
}
https://play.golang.com/p/nQqvUIROqF-

Golang mutate a struct's field one by one using reflect

I have a struct like this:
type User struct {
Name string
UID int
Bio string
}
I have a given instantiated struct, and I want to loop through the fields in that object and modify them one by one.
This is what I have so far
user := User{
Name: "Test",
UID: 1,
Bio: "Test bio",
}
reflectVal := reflect.ValueOf(user)
numFields := reflectVal.NumField()
for i := 0; i < numFields; i++ {
fieldType := reflect.TypeOf(reflectVal.Field(i))
reflectVal.Field(i).Set(reflect.Zero(fieldType))
...
}
But I'm getting this error:
panic: reflect: reflect.Value.Set using unaddressable value
Is there a way to do this?
The reflect value is not addressable. Fix by creating the reflect value from a pointer to the struct.
reflectVal := reflect.ValueOf(&user).Elem()
Use the following statement to get the field's type. The code in the question gets the type of the reflect.Value, not the type of the value contained within the reflect.Value.
fieldType := reflectVal.Field(i).Type()
Run it on the Go Playground.

Check if struct field is empty

I would like to iterate over the fields of a struct after unmarshalling a JSON object into it and check for the fields whose value was not set (i.e. are empty).
I can get the value of each field and compare that to the reflect.Zero value for the corresponding type
json.Unmarshal([]byte(str), &res)
s := reflect.ValueOf(&res).Elem()
typeOfT := s.Type()
for i := 0; i < s.NumField(); i++ {
f := s.Field(i)
v := reflect.ValueOf(f.Interface())
if (reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()).Interface())) {
....
But the problem, of course, is that this will not work well for bool or int values.
If a bool field is set to false in the JSON or an int field is set to 0, they will be equal to the zero value of their type. The aforementioned check will consider the fields to be uninitialized, even though they actually have a value set.
I know one way around this is to use pointers, but I just don't see how that would be possible in this case as I'm working with reflect.Value types, not the actual struct.
As you've mentioned, you could use pointers.
The json package can handle unmarshalling values into pointers for you. You've not included the json payload you are trying to unmarshal, or the struct you are unmarshalling into, so I've made up an example.
// json
{
"foo": true,
"number_of_foos": 14
}
// go struct
type Foo struct {
Present bool `json:"foo"`
Num int `json:"number_of_foos"`
}
Here if the keys foo or number_of_foos is missing, then as you've correctly observed, the zero value (false/ 0) will be used. In general the best advice is to make use of the zero value. Create structures so that zero values of false are useful, rather than a pain.
This is not always possible, so changing the types of the fields in the Foo struct to be pointers will allow you to check the 3 cases you are after.
Present
Present and zero
Missing
here is the same struct with pointers:
// go struct
type Foo struct {
Present *bool `json:"foo"`
Num *int `json:"number_of_foos"`
}
Now you can check for presence of the value with fooStruct.Present != nil and if that condition holds, you can assume that the value in the field is the one you wanted.
There is no need to use the reflect package.
Another way of doing the same is by implementing json.Unmarshaler.
type MaybeInt struct {
Present bool
Value int
}
func (i *MaybeInt) UnmarshalJSON(bs []byte) error {
if e := json.Unmarshal(bs, &i.Value); e != nil {
return e
}
i.Present = true
return nil
}
You can then use MaybeInt in your top-level structure:
type Top struct {
N MaybeInt `json:"n"`
M MaybeInt `json:"m"`
}
func main() {
t := Top{}
if e := json.Unmarshal([]byte(` { "n": 4930 } `), &t); e != nil {
panic(e)
}
fmt.Println(t.N, t.M)
}
See it working on the playground
Try using the golang validator package. The validator package offers a required attribute that might do the required job for your need. The official documentation for required attribute states:
This validates that the value is not the data types default zero value. For numbers ensures value is not zero. For strings ensures value is not "". For slices, maps, pointers, interfaces, channels and functions ensures the value is not nil.
The example illustrating the same can be seen at: https://github.com/go-playground/validator/blob/v9/_examples/struct-level/main.go.
Hope this solves your requirement.

Does type assertion change the value in go?

Go newbie here.
I have a map where the key arguments should be []string.
However, if I try to use the value directly arguments := m["arguments"] it doesn't seem to be the right type. When used later to append to another slice with arguments... I get Cannot use 'arguments' (type interface{}) as type []string.
I fixed this by chaning the assignment to a type check arguments, _ := m["arguments"].([]string). That works, but I'm not sure why. Is type assertion doing conversion as well?
The full example is below:
import (
"github.com/fatih/structs"
"strings"
)
var playbookKeyDict = map[string]string{
"Playbook": "",
"Limit" : "--limit",
"ExtraVars" : "--extra-vars",
}
type Playbook struct {
Playbook string `json:"playbook" xml:"playbook" form:"playbook" query:"playbook"`
Limit string `json:"limit" xml:"limit" form:"limit" query:"limit"`
ExtraVars string `json:"extra-vars" xml:"extra-vars" form:"extra-vars" query:"extra-vars"`
Arguments []string `json:"arguments" xml:"arguments" form:"arguments" query:"arguments"`
Args []string
}
func (p *Playbook) formatArgs() {
// is it worth iterating through directly with reflection instead of using structs import?
// https://stackoverflow.com/questions/21246642/iterate-over-string-fields-in-struct
m := structs.Map(p)
// direct assignment has the wrong type?
// arguments := m["arguments"]
arguments, _ := m["arguments"].([]string)
delete(m, "arguments")
for k, v := range m {
// Ignore non-strings and empty strings
if val, ok := v.(string); ok && val != "" {
key := playbookKeyDict[k]
if key == "" {
p.Args = append(p.Args, val)
} else {
p.Args = append(p.Args, playbookKeyDict[k], val)
}
}
}
p.Args = append(p.Args, arguments...)
}
Type assertion is used to get a value wrapped around using interface.
m := structs.Map(p)
Map(v interface{}){}
Map function is actually taking interface as its argument in the case stated. It is wrapping the type which is []string and its underlying value which is slice. The type can be checked using Relection reflect.TypeOf().
func TypeOf(i interface{}) Type
According to Russ Cox blog on Interfaces
Interface values are represented as a two-word pair giving a pointer
to information about the type stored in the interface and a pointer to
the associated data.
As specified in Golang spec
For an expression x of interface type and a type T, the primary
expression
x.(T)
asserts that x is not nil and that the value stored in x is of type T.
The notation x.(T) is called a type assertion.
For the error part:-
Cannot use 'arguments' (type interface{}) as type []string
We first needs to get the underlying value of type []string from interface using type assertion.

Resources