从接口中获取结构体的值

huangapple go评论92阅读模式
英文:

Get struct value from interface

问题

我正在尝试创建一个文件解析器,可以解析多种类型的数据(用户、地址等)到一个结构体中。为此,我创建了一个名为Datatype的接口:

package main

type Datatype interface {
	name() string
}

然后创建了几个实现该接口的结构体:

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"
}

然后,我读取文件名,根据文件名确定数据类型,并创建该结构体的实例传递给解析器:

func Parsefile(file string, dtype Datatype) ([]Datatype, error) {
   // 在这里进行解析文件的操作
}

我这样做是希望能够创建一个解析方法,该方法可以接受任何一个结构体,检测其类型并从CSV记录中进行反序列化。然而,我发现我不能这样做,因为我似乎无法从接口中获取底层类型,至少在我的Unmarshal函数中无法做到:

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
}

在上面的函数中,当它执行到尝试确定Datatype字段数量的那一行时,会出现以下错误:

panic: reflect: call of reflect.Value.NumField on interface Value

我知道我做得不好,我觉得一定有一种方法可以实现这种模式,而不必针对每种数据类型编写特定的逻辑。然而,无论如何,我都无法弄清楚如何在Go中实现这一点。

英文:

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.
<pre>package main

type User struct {
Username string validate:&quot;nonzero&quot;
FirstName string validate:&quot;nonzero&quot;
LastName string validate:&quot;nonzero&quot;
Email string validate:&quot;regexp=^[0-9a-zA-Z]+@[0-9a-zA-Z]+(\\.[0-9a-zA-Z]+)+$&quot;
Phone string validate:&quot;min=10&quot;
DateOfBirth string
}

type Users []User

func (u User) name() string {
return "user"
}</pre>

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:

<pre>
func Parsefile(file string, dtype Datatype) ([]Datatype, error) {
// Do stuff in here to parse the file
</pre>

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(&quot;Record: &quot;, record)
    if err != nil {
	    return err
    }
    s := reflect.ValueOf(v).Elem()
    if s.NumField() != len(record) {
	    return &amp;FieldMismatch{s.NumField(), len(record)}
    }
    for i := 0; i &lt; s.NumField(); i++ {
	    f := s.Field(i)
	    switch f.Type().String() {
	    case &quot;string&quot;:
		    f.SetString(record[i])
	    case &quot;int&quot;:
		    ival, err := strconv.ParseInt(record[i], 10, 0)
		    if err != nil {
			    return err
		    }
		    f.SetInt(ival)
	    default:
		    return &amp;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:

<pre>panic: reflect: call of reflect.Value.NumField on interface Value</pre>

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.

答案1

得分: 5

看起来你正在尝试使用来自这个问题的CSV解组代码。该代码的设计是在你有一个预分配的特定类型的结构体来传递给它时才能正常工作。你遇到问题是因为v在静态上是一个接口类型,即使传入的特定值是一个结构体。

我建议在你的接口和每个子类型上添加一个Unmarshal方法:

func (u *User) populateFrom(reader *csv.Reader) string {
    Unmarshal(reader, u) 
}

Go语言中还有一个很酷的东西叫做type switch

var i interface{}
switch t := v.(type) {
    case *User:
       i := t // i现在是一个*User。
    case *Address:
       i := t // i现在是一个*Address。
    default:
       panic("unknown type")
}
Unmarshal(reader, i)

希望对你有帮助!

英文:

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(&quot;unknown type&quot;)
}
Unmarshal(reader,i)

huangapple
  • 本文由 发表于 2015年4月18日 11:21:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/29712513.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定