Am am attempting to create a file parser that can parse multiple types of data (users, addresses, etc) into a struct. To do this I have created an interface called Datatype:
package main
type Datatype interface {
name() string
}
And several structs that implement the interface:
ex.
package main type User struct { Username string `validate:"nonzero"` FirstName string `validate:"nonzero"` LastName string `validate:"nonzero"` Email string `validate:"regexp=^[0-9a-zA-Z]+@[0-9a-zA-Z]+(\\.[0-9a-zA-Z]+)+$"` Phone string `validate:"min=10"` DateOfBirth string } type Users []User func (u User) name() string { return "user" }
I then read the filename, get the type of data it contains from the name of the file and create an instance of that struct to pass to the parser:
func Parsefile(file string, dtype Datatype) ([]Datatype, error) { // Do stuff in here to parse the file
I did this hoping I could create a parse method that took any one of the structs, detect the type and unmarshall from the csv record. However, what I am finding is that I can't do it this was as I can't seem to get the the underlying type from the interface. Or at least not with my Unmarshall function:
func Unmarshal(reader *csv.Reader, v *Datatype) error {
record, err := reader.Read()
fmt.Println("Record: ", record)
if err != nil {
return err
}
s := reflect.ValueOf(v).Elem()
if s.NumField() != len(record) {
return &FieldMismatch{s.NumField(), len(record)}
}
for i := 0; i < s.NumField(); i++ {
f := s.Field(i)
switch f.Type().String() {
case "string":
f.SetString(record[i])
case "int":
ival, err := strconv.ParseInt(record[i], 10, 0)
if err != nil {
return err
}
f.SetInt(ival)
default:
return &UnsupportedType{f.Type().String()}
}
}
return nil
}
In the above function I get the following error when it hits the line trying to determine the number of fields in the Datatype:
panic: reflect: call of reflect.Value.NumField on interface Value
I know I am going about this poorly and I feel there must be a way to achieve this pattern without having to write logic specific to each data type. However, for the life of my I cannot figure out how to achieve this in go.
It appears you are trying to use the csv unmarshalling code from this question. That is designed to work when you have a pre-allocated struct of a specific type to pass into it. You are having issues because v is statically an interface type even though the particular value passed in is a struct.
I would recommend putting an Unmarshal method on your interface and on each subType:
func (u *User) populateFrom(reader *csv.Reader) string {
Unmarshal(reader, u)
}
Another cool thing in go is the type switch:
var i interface{}
switch t := v.(type) {
case *User:
i := t // i is now a *User.
case *Address:
i := t // i is now a *Address.
default:
panic("unknown type")
}
Unmarshal(reader,i)