Golang接口到结构体的转换

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

Golang interface to struct

问题

我有一个函数,其中一个参数的类型是interface{},类似于:

func LoadTemplate(templateData interface{}) {

在我的情况下,templateData是一个结构体,但每次它的结构都不同。我使用了"type interface{}",因为它允许我发送各种类型的数据。

我正在使用templateData将数据发送到模板:

err := tmpl.ExecuteTemplate(w, baseTemplateName, templateData)

但现在我想要添加一些新的数据,但我不知道如何做,因为"interface"类型不允许我添加/追加任何内容。

我尝试将接口转换为结构体,但我不知道如何向具有未知结构的结构体追加数据。

如果我使用以下函数,我可以查看接口的数据:

templateData = appendAssetsToTemplateData(templateData)

func appendAssetsToTemplateData(t interface{}) interface{} {
    switch reflect.TypeOf(t).Kind() {
    case reflect.Struct:
        fmt.Println("struct")
        s := reflect.ValueOf(t)
        fmt.Println(s)
        
        //基于当前接口数据创建一个新的结构体
    }

    return t
}

有什么办法可以向初始接口参数(templateData)追加一个子项?或者如何将其转换为结构体或其他类型,以便追加新的子项/数据?

英文:

I have a function that has a parameter with the type interface{}, something like:

func LoadTemplate(templateData interface{}) {

In my case, templateData is a struct, but each time it has a different structure. I used the type "interface{}" because it allows me to send all kind of data.

I'm using this templateData to send the data to the template:

err := tmpl.ExecuteTemplate(w, baseTemplateName, templateData)

But now I want to append some new data and I don't know how to do it because the "interface" type doesn't allow me to add/append anything.

I tried to convert the interface to a struct, but I don't know how to append data to a struct with an unknown structure.

If I use the following function I can see the interface's data:

templateData = appendAssetsToTemplateData(templateData)

func appendAssetsToTemplateData(t interface{}) interface{} {
	switch reflect.TypeOf(t).Kind() {
	case reflect.Struct:
		fmt.Println("struct")
		s := reflect.ValueOf(t)
		fmt.Println(s)
		
		//create a new struct based on current interface data
	}

	return t
}

Any idea how can I append a child to the initial interface parameter (templateData)? Or how can I transform it to a struct or something else in order to append the new child/data?

答案1

得分: 14

Adrian是正确的。更进一步说,只有在你知道实现该接口的类型时,才能对接口进行任何操作。空接口interface{}并不是一个常见误解中的“任意值”,它只是一个立即被所有类型满足的接口。

因此,你只能从中获取值,或者通过在添加之前和之后了解满足空接口的类型来创建一个新的“接口”,并添加值。

在给定静态类型的情况下,你可以通过将之前的类型嵌入到之后的类型中来实现你想要的效果,这样一切仍然可以在之后类型的根部访问。以下是示例:

package main

import (
	"fmt"
)

type Before struct {
	m map[string]string
}

type After struct {
	Before
	s []string
}

func contrivedAfter(b interface{}) interface{} {
	return After{b.(Before), []string{"new value"}}
}

func main() {
	b := Before{map[string]string{"some": "value"}}
	a := contrivedAfter(b).(After)
	fmt.Println(a.m)
	fmt.Println(a.s)
}

此外,由于你传递给模板的数据不需要指定类型,你可以使用匿名结构体来实现非常相似的效果。以下是示例:

package main

import (
	"fmt"
)

type Before struct {
	m map[string]string
}

func contrivedAfter(b interface{}) interface{} {
	return struct {
		Before
		s []string
	}{b.(Before), []string{"new value"}}
}

func main() {
	b := Before{map[string]string{"some": "value"}}
	a := contrivedAfter(b)
	fmt.Println(a)
}
英文:

Adrian is correct. To take it a step further, you can only do anything with interfaces if you know the type that implements that interface. The empty interface, interface{} isn't really an "anything" value like is commonly misunderstood; it is just an interface that is immediately satisfied by all types.

Therefore, you can only get values from it or create a new "interface" with added values by knowing the type satisfying the empty interface before and after the addition.

The closest you can come to doing what you want, given the static typing, is by embedding the before type in the after type, so that everything can still be accessed at the root of the after type. The following illustrates this.

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

package main

import (
	"fmt"
)

type Before struct {
	m map[string]string
}

type After struct {
	Before
	s []string
}

func contrivedAfter(b interface{}) interface{} {
	return After{b.(Before), []string{"new value"}}
}

func main() {
	b := Before{map[string]string{"some": "value"}}
	a := contrivedAfter(b).(After)
	fmt.Println(a.m)
	fmt.Println(a.s)
}

Additionally, since the data you are passing to the template does not require you to specify the type, you could use an anonymous struct to accomplish something very similar.

https://play.golang.org/p/3KUfHULR84

package main

import (
	"fmt"
)

type Before struct {
	m map[string]string
}

func contrivedAfter(b interface{}) interface{} {
	return struct{
		Before
		s []string
	}{b.(Before), []string{"new value"}}
}

func main() {
	b := Before{map[string]string{"some": "value"}}
	a := contrivedAfter(b)
	fmt.Println(a)
}

答案2

得分: 4

你不能随意向结构体添加数据;它们是静态类型的。你只能为特定的结构体类型定义的字段赋值。你最好使用map而不是结构体来实现这个目的。

英文:

You can't append data arbitrarily to a struct; they're statically typed. You can only assign values to the fields defined for that specific struct type. Your best bet is probably to use a map instead of structs for this.

答案3

得分: 2

不建议这样做,但是你可以使用reflect包动态创建结构体。

以下是一个示例:

package main

import (
	"encoding/json"
	"os"
	"reflect"
)

type S struct {
	Name string
}

type D struct {
	Pants bool
}

func main() {
	a := Combine(&S{"Bob"}, &D{true})
	json.NewEncoder(os.Stderr).Encode(a)
}

func Combine(v ...interface{}) interface{} {
	f := make([]reflect.StructField, len(v))
	for i, u := range v {
		f[i].Type = reflect.TypeOf(u)
		f[i].Anonymous = true
	}

	r := reflect.New(reflect.StructOf(f)).Elem()
	for i, u := range v {
		r.Field(i).Set(reflect.ValueOf(u))
	}
	return r.Addr().Interface()
}

你可以使用上面的Combine函数将任意数量的结构体组合在一起。不幸的是,根据文档

StructOf目前不会为嵌入字段生成包装方法。这个限制可能在将来的版本中解除。

因此,你创建的结构体不会继承嵌入类型的方法。不过,也许它能满足你的需求。

英文:

Not recommended, but you can create structs dynamically using the reflect package.

Here is an example:

package main

import (
	"encoding/json"
	"os"
	"reflect"
)

type S struct {
	Name string
}

type D struct {
	Pants bool
}

func main() {
	a := Combine(&S{"Bob"}, &D{true})
	json.NewEncoder(os.Stderr).Encode(a)
}

func Combine(v ...interface{}) interface{} {
	f := make([]reflect.StructField, len(v))
	for i, u := range v {
		f[i].Type = reflect.TypeOf(u)
		f[i].Anonymous = true
	}

	r := reflect.New(reflect.StructOf(f)).Elem()
	for i, u := range v {
		r.Field(i).Set(reflect.ValueOf(u))
	}
	return r.Addr().Interface()
}

You could use something like the Combine function above to shmush any number of structs together. Unfortunately, from the documentation:

> StructOf currently does not generate wrapper methods for embedded fields. This limitation may be lifted in a future version.

So your created struct won't inherit methods from the embedded types. Still, maybe it does what you need.

答案4

得分: -1

如果你只是想将你的接口转换为结构体,请使用以下方法。

type Customer struct {
    Name string `json:"name"`
}

func main() {
    // 创建一个客户,将其添加到DTO对象并进行编组
    receivedData := somefunc() // 返回接口

    // 尝试从DTO中解组我们的客户
    receivedCustomer := getCustomerFromDTO(receivedData)
    fmt.Println(receivedCustomer)
}

func getCustomerFromDTO(data interface{}) Customer {
    m := data.(map[string]interface{})
    customer := Customer{}
    if name, ok := m["name"].(string); ok {
        customer.Name = name
    }
    return customer
}

希望对你有帮助!

英文:

If you are just looking to convert your interface to struct, use this method.

type Customer struct {
    Name string `json:"name"`
}

func main() {
    // create a customer, add it to DTO object and marshal it
    receivedData := somefunc() //returns interface

    //Attempt to unmarshall our customer
    receivedCustomer := getCustomerFromDTO(receivedData)
    fmt.Println(receivedCustomer)
}

func getCustomerFromDTO(data interface{}) Customer {
    m := data.(map[string]interface{})
    customer := Customer{}
    if name, ok := m["name"].(string); ok {
        customer.Name = name
    }
    return customer
}

huangapple
  • 本文由 发表于 2017年6月28日 22:53:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/44805984.html
匿名

发表评论

匿名网友

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

确定