Reputation: 3021
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.
Upvotes: 2
Views: 3481
Reputation: 23068
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)
Upvotes: 5