0

I have several structs that are receiving data. All structs shall include some of the same data, which have been embedded with the HeaderData struct. The data is not filled at the same time, and I need a function to check if all the fields have received a value (is not an empty string).

I tried to solve this with reflect. The problem is that reflect will consider the HeaderData to be one field. This means that it will always be a non-empty string, although it may include empty fields. So I believe that I need a way to check that struct separately.

I tried to access it with anyStruct.HeaderData, but that is not working since “{} is interface with no methods”.

Is there any other way to access the HeaderData so that this works?

Or can I in some way specify in the dataReady that the input must have the field HeaderData?

package main

import (
    "fmt"
    "reflect"
)

type HeaderData struct {
    Param1  string
    Param2  string
}

type Data1 struct {
    HeaderData 
    Param3  string
    Param4  string
}

type Data2 struct {
    HeaderData 
    Param3  string
    Param5  string
}

func dataReady(anyStruct interface{}) bool {
    v := reflect.ValueOf(anyStruct)
    for i := 0; i < v.NumField(); i++ {
        // fmt.Println(v.Field(i).Interface())
        if v.Field(i).Interface() == "" {
            return false
        }
    }


    // v1 := reflect.ValueOf(anyStruct.HeaderData)
    // Not working:
    // anyStruct.HeaderData undefined (type interface {} is interface with no methods)

    return true
}

func main() {
    d1 := Data1{HeaderData: HeaderData{Param1: "ABC", Param2: "DEF"}, Param3: "GHI", Param4: "JKL"}
    d2 := Data2{HeaderData: HeaderData{Param1: "ABC", Param2: "DEF"}}
    d3 := Data2{HeaderData: HeaderData{Param1: "ABC"}, Param3: "GHI", Param5: "JKL"}
    d4 := Data2{Param3: "GHI", Param5: "JKL"}
    fmt.Println("d1Ready: ", dataReady(d1))     //Returns true, which is correct
    fmt.Println("d2Ready: ", dataReady(d2))     //Returns false, which is correct
    fmt.Println("d3Ready: ", dataReady(d3))     //Returns true but should return false
    fmt.Println("d4Ready: ", dataReady(d4))     //Returns true but should return false
}

Playground

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Jimbo
  • 43
  • 6
  • Why do you need reflect for this? Have a method/function for checking the fields of `HeaderData` and methods/unctions for those `DataN` structs; the methods/functions on the latter would call to the method/function which would check the fields of the HeaderData _field_ in them (the fact it's embedded does not preclude accessing it via the name of its type). – kostix Mar 05 '20 at 11:54
  • You might think this is "too much code" but in exchange it's more understandable (and way more faster). – kostix Mar 05 '20 at 11:55
  • @kostix That is an option, and maybe I should do it like that. But as you mention it will be more code, I will need one function per struct. Also If there is a change in a struct I will have to update the function. So I thought it would be nice if I had a more generic way of checking this. – Jimbo Mar 05 '20 at 12:02

1 Answers1

0

Perhaps try a little interface to do the same thing and i think that this would be a lot more readable.

https://play.golang.org/p/H4lh91RMd6C

package main

import (
     "fmt"
)

 type(

     FieldCheck interface {
         Check() (b bool)
     }

     HeaderData struct {
         Param1  string
         Param2  string
     }

    Data1 struct {
         HeaderData
         Param3  string
         Param4  string
     }

     Data2 struct {
         HeaderData
         Param3  string
         Param5  string
     }
  )

func(h *HeaderData) Check() (b bool) {
     if h.Param1 == "" || h.Param2 == "" {
         return false
     }
     return true
}

func(d *Data1) Check() (b bool) {
     if (d.Param3 == "" || d.Param4 == "" || ! d.HeaderData.Check()) {
         return false
     }
     return true
}

func(d *Data2) Check() (b bool) {
     if (d.Param3 == "" || d.Param5 == "" || ! d.HeaderData.Check()) {
         return false
     }
     return true
 }


func main() {

    d := []FieldCheck{
         &Data1{HeaderData: HeaderData{Param1: "ABC", Param2: "DEF"}, Param3: 
"GHI", Param4: "JKL"},
        &Data2{HeaderData: HeaderData{Param1: "ABC", Param2: "DEF"}},
        &Data2{HeaderData: HeaderData{Param1: "ABC"}, Param3: "GHI", Param5: 
"JKL"},
        &Data2{Param3: "GHI", Param5: "JKL"},
    }

    for _, num := range d {
        fmt.Println(num.Check())
    }
}
John Dowling
  • 582
  • 6
  • 23
  • Thanks for the suggestion! Yes, it seem like that is the way to go. The drawback is that if a field is added to a struct, the Check method must also be updated with the new field. But hopefully the structs will be quite stable :-) – Jimbo Mar 05 '20 at 12:44
  • I did it like that for simplicity, you can loop the fields at each level of the struct and therefore not have do this. you could also have a type assertion to make it a little more dynamic. – John Dowling Mar 05 '20 at 12:47
  • That’s true. Thanks :-) – Jimbo Mar 06 '20 at 08:19