Golang count number of fields in a struct of structs - go

Given a struct CompleteStruct that is composed of two embedded structs StructA and StructB where StructB has ImbStructC in it.
type StructA struct {
AA int
AB int
AC int
}
type ImbStructC struct {
BCC int
}
type StructB struct {
BA int
BB int
ImbStructC
}
type CompleteStruct struct {
StructA
StructB
}
How do you extract the total number of fields in the inner struct?
reflect.TypeOf(CompleteStruct{}).NumField())
returns 2, I assume because CompleteStruct is made up of 2 embedded structs.
What code can I use to show that CompleteStruct has 6 fields?

Recursion is needed to solve this, as an embedded struct field may itself embed another struct.
Also, one should be careful not to count embedded structs as field - these are listed as "anonymous" fields in the reflect package:
func countFields(v any) int {
return rvCountFields(reflect.ValueOf(v))
}
func rvCountFields(rv reflect.Value) (count int) {
if rv.Kind() != reflect.Struct {
return
}
fs := rv.NumField()
count += fs
for i := 0; i < fs; i++ {
f := rv.Field(i)
if rv.Type().Field(i).Anonymous {
count-- // don't count embedded structs (listed as anonymous fields) as a field
}
// recurse each field to see if that field is also an embedded struct
count += rvCountFields(f)
}
return
}
https://go.dev/play/p/IjOllo86_xk
Output:
main.CompleteStruct : count = 5
main.StructA : count = 3
main.StructB : count = 2
main.StructC : count = 6
main.StructD : count = 12
main.Empty : count = 0
int : count = 0

The reflect.VisibleFields package lists all the fields in the struct, from there is "just" a matter of counting those fields that are not Anonymous.
func CountNumberOfFieldsInAStruct(obj interface{}) int {
fields := reflect.VisibleFields(reflect.TypeOf(obj))
count := 0
for _, field := range fields {
if !field.Anonymous {
count += 1
}
}
return count
}
As #colm.anseo mentions "this will only expose the "visible" exported i.e. capitalized struct field names. It will not include lowercase (unexported) struct fields. So if you only want exported fields, then this would work"
Tested here
https://go.dev/play/p/Ea-y8YAkcqZ

Basically you need to get the number of fields from the parent struct and then loop through the children and get the number of fields for each child and then add them up
innerFields := 0
numOfFields := reflect.TypeOf(CompleteStruct{}).NumField()
for i := 0; i < numOfFields; i++ {
innerFields += reflect.TypeOf(CompleteStruct{}).Field(i).Type.NumField()
}
This code is tested and works

Related

go generics: processing different struct types with same data member types [duplicate]

This question already has answers here:
How can I access a struct field with generics (type T has no field or method)?
(1 answer)
Generic function to work on different structs with common members from external package?
(1 answer)
Closed 3 months ago.
There are two struct types, Foo and Bar, with an int data member val. I am trying to write a generic function that can handle both types. I tried the following and this did not work.
package main
import "fmt"
type Foo struct {
val int
}
type Bar struct {
val int
}
func Add[T any](slice []T) int {
var sum int
for _, elem := range slice {
sum += elem.val
}
return sum
}
func Test() {
f1 := Foo{val: 2}
f2 := Foo{val: 2}
fslice := []Foo{f1, f2}
fsum := Add(fslice)
fmt.Printf("fsum = %d\n", fsum)
b1 := Bar{val: 3}
b2 := Bar{val: 3}
bslice := []Bar{b1, b2}
bsum := Add(bslice)
fmt.Printf("bsum = %d\n", bsum)
}
func main() {
Test()
}
The compiler throws the following error.
$ go run generics1.go
# command-line-arguments
./generics1.go:16:15: elem.val undefined (type T has no field or method val)
Go playground link: https://go.dev/play/p/mdOMH3xuwu7
What could be a possible way to approach this?
Per golang 1.18 release note
The Go compiler does not support accessing a struct field x.f where x is of type parameter type even if all types in the type parameter's type set have a field f. We may remove this restriction in a future release.
You could define one GetVal() interface method to retrieve the val, and use this method as part of type constraint of generic.
Sample codes
type Foo struct {
val int
}
func (f Foo) GetVal() int {
return f.val
}
type Bar struct {
val int
}
func (b Bar) GetVal() int {
return b.val
}
type MyType interface {
Foo | Bar
GetVal() int
}
func Add[T MyType](slice []T) int {
var sum int
for _, elem := range slice {
sum += elem.GetVal()
}
return sum
}
https://go.dev/play/p/0eJZpqy7q8f

Iterating slice struct within struct using reflection

I'm trying to achieve the following:
Use-case:
I have three structures, I need to compare 2 of those against one. (in the example described as: a & b need to be compared against full)
Reflection is used to loop over every field, retrieve the name of the field. And comparing the difference between a & full, b & full, storing the results in a shared structure.
If the field equals World, we know it's a slice struct:
I need to retrieve the first index of the Bar slice within the Foo structure.
Even though the variable is a slice, I know it will always have a length of 1 in this use-case.
When retrieved I need to loop over those fields, like what is happening in the previous if statement.
Example code:
type Foo struct {
Hello string
World []Bar
}
type Bar struct {
Fish string
}
type Result struct {
Field string
Correct_A bool
Distance_A int
Correct_B bool
Distance_B int
Result []Result
}
func compare_structs() {
var full, a, b Foo
// filling in all variables...
result := []Result{}
rfx_f := reflect.ValueOf(full)
rfx_a := reflect.ValueOf(a)
rfx_b := reflect.ValueOf(b)
type_result := rfx_f.Type()
for i := 0; i < rfx_f.NumField(); i++ {
tmp_res := Result{
Field: type_result.Field(i).Name,
}
if reflect.TypeOf(full).Field(i).Type.Kind() != reflect.Slice {
value := rfx_f.Field(i).Interface()
value_a := rfx_a.FieldByName(tmp_res.Field).Interface()
value_b := rfx_b.FieldByName(tmp_res.Field).Interface()
// functions to compare the values of this field
tmp_res.compare(value, value_a, value_b)
tmp_res.lev(value, value_a, value_b)
result = append(result, tmp_res)
} else if tmp_res.Field == "World" {
/*
I need to retrieve the first index of the Bar slice within the Foo structure.
Even though the variable is a slice, I know it will always have a length of 1 in this use-case.
When retrieved I need to loop over those fields, like what is happening in the previous if statement.
*/
}
}
}
You first need to get the field:
wordField:=rfx_f.Field(i)
which you know to be a slice, so you index it to get the first element
item:=wordField.Index(0)
This will panic if index is out of range.
Then you can iterate the fields:
for fieldIx:=0;fieldIx<item.NumField();fieldIx++ {
field:=item.Field(fieldIx)
}

access index of struct item field in Golang

Is there any possibility to change character with some index in struct field if it is string?
I mean I can do such manipulations with string type:
func main() {
v := "Helv"
v[3] = "p"
}
How can I do same thing with struct fields? Below assignment doesn't work.
type ik struct {
a int
b string
}
func main() {
f := ik{1, "Pasis"}
fmt.Println(f.b)
f.b[2] = "r"
}
As strings are immutable in Go, you need to reassign another string to the variable. You can achieve this with following slice trick,
chars := "Helv"
// To replace the character in i'th index,
chars = chars[:i] + "p" + chars[i+1:]
So, your program becomes,
type ik struct {
a int
b string
}
func main() {
chars := "Helv"
chars = chars[:3] + "p" + chars[4:]
fmt.Println(chars)
f := ik{1, "Pasis"}
fmt.Println(f.b)
f.b = f.b[:2] + "r" + f.b[3:]
fmt.Println(f.b)
}
Go playground: https://play.golang.org/p/flERg0MVXLE

How can I compare two structs of not the same type?

How can I check if two structs that are not of the same type are equal?
Meaning if we have struct of typeA and struct of typeB, if in both structs we have same amount of fields with the same types - they are equal.
type layoutA struct {
A int
}
type layoutB layoutA
reflect.TypeOf(layoutA{}) == reflect.TypeOf(layoutB{}) // false
cmp.Equal(layoutA{}, layoutB{}) // false
compareStructs(layoutA{}, layoutB{}) // true - need to find this function
cmp package -> "github.com/google/go-cmp/cmp"
If the order of the fields needs to match as well you can just check for "convertability" using the ConvertibleTo method of the reflect.Type type.
If the order doesn't matter then you'll have to compare each individual field, a bit more work, but still very basic. First check that both types have the same number of fields, then loop over the fields of one type and check that every field that's present in that type is also present in the other type by using the FieldByName method and then compare the types of those fields.
You can compare the field types by using reflection with something like this:
func compareStructs(s1 interface{}, s2 interface{}) bool {
type1 := reflect.TypeOf(s1)
type2 := reflect.TypeOf(s2)
numField1 := type1.NumField()
numField2 := type2.NumField()
if numField1 != numField2 {
return false
}
for i := 0; i < numField1; i++ {
field1 := type1.Field(i)
field2 := type2.Field(i)
if field1.Type != field2.Type {
return false
}
}
return true
}

How to dynamically create a struct with one less property?

Is there a way to copy a generic struct (i.e. a struct whose property names are unknown) and skip a single, known property?
Here is what I know:
The parameter to my function--I will call the parameter myData-- is of type interface{}.
myData is a struct.
myData has a known property path.
myData has anywhere from 0 to 6 or so other properties, none of which are known a priori.
Once I remove that path property, then the “leftover” is one of say 30 possible struct types.
So I want to strip path out of myData (or more accurately make a copy that omits path) so that various bits of generated code that try to coerce the struct to one of its possible types will be able to succeed.
I have found examples of copying a struct by reflection, but they typically create an empty struct of the same underlying type, then fill it in. So is it even possible to delete a property as I have outlined...?
You can use reflect.StructOf to dynamically create structs from a list of fields.
package main
import (
"fmt"
"reflect"
)
type A struct {
Foo string
Bar int
Baz bool // to be skipped
}
type B struct {
Foo string
Bar int
}
func main() {
av := reflect.ValueOf(A{"hello", 123, true})
fields := make([]reflect.StructField, 0)
values := make([]reflect.Value, 0)
for i := 0; i < av.NumField(); i++ {
f := av.Type().Field(i)
if f.Name != "Baz" {
fields = append(fields, f)
values = append(values, av.Field(i))
}
}
typ := reflect.StructOf(fields)
val := reflect.New(typ).Elem()
for i := 0; i < len(fields); i++ {
val.Field(i).Set(values[i])
}
btyp := reflect.TypeOf(B{})
bval := val.Convert(btyp)
b, ok := bval.Interface().(B)
fmt.Println(b, ok)
}

Resources