如何将 interface{} 转换回原始的结构体类型?

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

How to cast interface{} back into its original struct?

问题

我需要一种将结构体/接口动态转换回其原始对象的方法。
我可以在内部添加方法/函数。基本上我需要这样的东西:

MyStruct => Interface{} => MyStruct

当进行最终转换时,我除了结构体内部的内容之外,对原始结构体一无所知,所以我不能只这样写:

a.(MyStruct)
英文:

I need a way to dynamically cast a struct/interface back to its original object.
I can add methods / functions inside. basically I need something like this:

MyStruct  =>  Interface{}  => MyStruct

When on the final conversion I don't know anything about the original struct besides what come inside the struct, so I can't just so:

a.(MyStruct)

答案1

得分: 11

你需要至少了解可能的类型。有几种情况:1. 你认为你可能知道它是什么类型。2. 你有一个可能的类型列表。3. 你的代码对底层类型一无所知。

  1. 如果你认为你知道它是什么类型,你可以使用类型断言将其转换回原始的结构体类型。

...

package main

import (
	"fmt"
)

type MyStruct struct {
  Thing string
}

func (s *MyStruct) Display() {
  fmt.Println(s.Thing)
}

type Thingable interface {
  Display()
}

func main() {
  s := &MyStruct{
    Thing: "Hello",
  }

  // 以 MyThing 的形式打印
  s.Display()

  var thinger Thingable
  thinger = s

  // 以 thingable 接口的形式打印
  thinger.Display()

  // 将 thinger 转换回 MyStruct
  s2 := thinger.(*MyStruct) // 这是 "类型断言",你断言 thinger 是指向 MyStruct 的指针。如果 thinger 不是 *MyStruct,这将引发 panic

  s2.Display()
}

你可以在这里看到实际效果:https://play.golang.org/p/rL12Lrpqsyu

注意,如果你想在测试类型时避免出错,可以使用 s2, ok := thinger.(*MyStruct)。如果成功,ok 将为 true,否则为 false。

  1. 如果你想将接口变量与一系列类型进行测试,可以使用 switch:(滚动到底部)

...

package main

import (
	"fmt"
	"reflect"
)

type MyStruct struct {
	Thing string
}

type MyStruct2 struct {
	Different string
}

func (s *MyStruct) Display() {
	fmt.Println(s.Thing)
}

func (s *MyStruct2) Display() {
	fmt.Println(s.Different)
}

type Thingable interface {
	Display()
}

func main() {
	s := &MyStruct{
		Thing: "Hello",
	}

	// 以 MyThing 的形式打印
	s.Display()

	var thinger Thingable
	thinger = s

	// 以 thingable 接口的形式打印
	thinger.Display()

	// 尝试识别 thinger
	switch t := thinger.(type) {
	case *MyStruct:
		fmt.Println("thinger 是 *MyStruct。Thing =", t.Thing)
	case *MyStruct2:
		fmt.Println("thinger 是 *MyStruct2。Different =", t.Different)
	default:
		fmt.Println("thinger 是未知类型:", reflect.TypeOf(thinger))
	}
}

你可以在这里尝试一下:https://play.golang.org/p/7NEbwB5j6Is

  1. 如果你真的对底层类型一无所知,你将不得不通过接口函数暴露所需的内容,并调用这些函数。很有可能你可以在不了解底层类型的情况下做到这一点。如果一切都失败了,你可以使用 reflect 包来检查你的接口对象并收集有关它的信息。这就是 json 包读取 json 文本并返回填充的结构体的方式,但这是一个高级主题,如果选择这条路线,你应该准备花费很多时间。最好将反射代码隐藏在具有清晰接口的包内(即包的 API)。
英文:

You need to know at least the possible types it could be. There's a couple cases, 1. You think you might know what it is. 2. You have a list of possible types it could be, 3. Your code knows nothing about the underlying types.

  1. If you think you know it, you can use type assertion to convert back to the original struct type.

...

package main

import (
	"fmt"
)

type MyStruct struct {
  Thing string
}

func (s *MyStruct) Display() {
  fmt.Println(s.Thing)
}

type Thingable interface {
  Display()
}

func main() {
  s := &MyStruct{
    Thing: "Hello",
  }

  // print as MyThing
  s.Display()

  var thinger Thingable
  thinger = s

  // print as thingable interface
  thinger.Display()

  // convert thinger back to MyStruct
  s2 := thinger.(*MyStruct) // this is "type assertion", you're asserting that thinger is a pointer to MyStruct. This will panic if thinger is not a *MyStruct

  s2.Display()
}

You can see this in action here: https://play.golang.org/p/rL12Lrpqsyu

Note if you want to test the type without panicking if you're wrong, do s2, ok := thinger.(*MyStruct). ok will be true if it was successful and false otherwise.

  1. if you want to test your interface variable against a bunch of types, use a switch: (scroll to bottom)

...

package main

import (
	"fmt"
	"reflect"
)

type MyStruct struct {
	Thing string
}

type MyStruct2 struct {
	Different string
}

func (s *MyStruct) Display() {
	fmt.Println(s.Thing)
}

func (s *MyStruct2) Display() {
	fmt.Println(s.Different)
}

type Thingable interface {
	Display()
}

func main() {
	s := &MyStruct{
		Thing: "Hello",
	}

	// print as MyThing
	s.Display()

	var thinger Thingable
	thinger = s

	// print as thingable interface
	thinger.Display()

	// try to identify thinger
	switch t := thinger.(type) {
	case *MyStruct:
		fmt.Println("thinger is a *MyStruct. Thing =", t.Thing)
	case *MyStruct2:
		fmt.Println("thinger is a *MyStruct2. Different =", t.Different)
	default:
		fmt.Println("thinger is an unknown type:", reflect.TypeOf(thinger))
	}
}

You can try that out here https://play.golang.org/p/7NEbwB5j6Is

  1. If you really don't know anything about the underlying types, you'll have to expose the things you need through interface functions and call those. Chances are you can do this without knowing anything about the underlying type. If all else fails, you can use the reflect package to introspect your interface object and gather information about it. this is how the json package reads json text and returns populated structs—though this is an advanced topic and expect to sink a lot of time into it if you go this route. it’s best to hide reflection code inside a package with a clean interface(ie the package api).

答案2

得分: 3

根据这个帖子中提到的内容:

  • Go语言既不是协变的也不是逆变的。类型要么相等,要么不相等。
  • 你要么将结构体拆开并处理各个部分,要么使用反射。
  • 类型断言只是"断言",而不是任何类型的"强制转换"。

另请参考这个帖子,它提醒我们:

  • 指针是一种类型。
  • 结构体是另一种类型。
  • 整数是另一种类型。
  • 浮点数是另一种类型。
  • 布尔值是另一种类型。

接口的原则涉及到附加到类型T的方法,而不是T的类型。

接口类型由一组方法定义。任何实现这些方法的值都可以赋值给该类型的接口值。

这将使得从接口到具体类型的转换变得非常困难。

英文:

No: as mentioned in this thread

> Go is neither covariant nor contravariant. Types are either equal or they aren't.

> You have to either take the structs apart and deal with the pieces, or use reflection.
Type assertions are only "assertions", not "coercions" of any kind.

See also this thread, which reminds us that:

> - A pointer is one kind of type.

  • A struct is another kind of type.
  • An integer is another kind of type.
  • A floating point number is another kind of type.
  • A boolean is another kind of type.

> The principle of an interface concerns the methods attached to a type T, not what type T is.

> An interface type is defined by a set of methods.
Any value that implements the methods can be assigned to an interface value of that type.

That would make the conversion from interface to concrete type quite difficult to do.

huangapple
  • 本文由 发表于 2017年1月15日 20:02:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/41660857.html
匿名

发表评论

匿名网友

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

确定