I'm trying to convert an interface dynamically back to it's original struct but I am having issues accessing attributes of the struct after the conversion.
Take this code for example.
package main
import (
"fmt"
"log"
)
type struct1 struct {
A string
B string
}
type struct2 struct {
A string
C string
}
type struct3 struct {
A string
D string
}
func main() {
s1 := struct1{}
s1.A = "A"
structTest(s1)
s2 := struct2{}
s2.A = "A"
structTest(s2)
s3 := struct3{}
s3.A = "A"
structTest(s3)
}
func structTest(val interface{}) {
var typedVal interface{}
switch v := val.(type) {
case struct1:
fmt.Println("val is struct1")
case struct2:
fmt.Println("val is struct2")
case struct3:
fmt.Println("val is struct3")
default:
log.Panic("not sure what val is.")
}
fmt.Println(typedVal.A)
}
I would like to be able to pass in one of 3 known struct types into my function. Then figure out which struct type was passed in to type assert it. Finally I want to be able to access like attributes.
Basically I want to have some basic inheritance in my structs, but so far it seems that it is not possible to do this in go. I saw some posts mentioning inheritance using an interface, but my structs have no methods so I'm not sure how I would use an interface.
Is something like this possible in go?
I would like to be able to pass in one of 3 known struct types into my function. Then figure out which struct type was passed in to type assert it. Finally I want to be able to access like attributes.
You can use type assertions to do exactly that. Basic idea is, in any case of the type switch just use type assertion to get a concrete instance of the corresponding type and then you can call whatever properties that you wish.
Take a look at the following example
package main
import (
"fmt"
)
type test1 struct {
A, B string
}
type test2 struct {
A, C string
}
func testType(val interface{}) {
switch val.(type) {
case test1:
t := val.(test1)
fmt.Println(t.B)
break
case test2:
t := val.(test2)
fmt.Println(t.C)
break
}
}
func main() {
t1, t2 := test1{B: "hello"}, test2{C: "world"}
testType(t1)
testType(t2)
}
Playground
Function structTest(val interface{}) in your code seems to be loosely typed. You pass it an untyped argument and expect it will satisfy some condition (will have field A), it looks strange in any typed language.
Using an interface this kind of polymorphism, in Go, to my mind, can be expressed something like
package main
import (
"fmt"
"log"
)
type A string
type HasA interface {
PrintA()
}
func (a A) PrintA() { fmt.Println(a) }
type struct1 struct {
A
B string
}
type struct2 struct {
A
C string
}
type struct3 struct {
A
D string
}
func main() {
s1 := struct1{}
s1.A = "A"
structTest(s1)
s2 := struct2{}
s2.A = "A"
structTest(s2)
s3 := struct3{}
s3.A = "A"
structTest(s3)
}
func structTest(val HasA) {
switch val.(type) {
case struct1:
fmt.Println("val is struct1")
case struct2:
fmt.Println("val is struct2")
case struct3:
fmt.Println("val is struct3")
default:
log.Panic("not sure what val is.")
}
val.PrintA()
}
Playground
Related
I understood that casting is being implemented in go using type assertion.
I'm trying to cast an object which is an instance of a struct that implements an interface.
My Code:
package main
import "fmt"
type Base interface {
Merge(o Base)
}
type Impl struct {
Names []string
}
func (i Impl) Merge (o Base) {
other, _ := o.(Impl)
i.Names = append(i.Names, other.Names...)
}
func main() {
impl1 := &Impl{
Names: []string{"name1"},
}
impl2 := &Impl{
Names: []string{"name2"},
}
impl1.Merge(impl2)
fmt.Println(impl1.Names)
}
Which outputs this:
[name1]
I expect the output to be:
[name1, name2]
Why this casting doesn't work? After debugging this it seems like that the other variable is empty.
You need to use a pointer method to modify the receiver's build.
func (i *Impl) Merge (o Base) {
other, _ := o.(*Impl)
i.Names = append(i.Names, other.Names...)
}
Playground: https://play.golang.org/p/7NQQnfJ_G6A
I want to merge some similar func code to one func, but every old func is use different type of struct, so i intend to create the model by different string of type.
SO i do something like this:
type A struct {
filed string
}
type B struct {
filed string
}
and still C, D, E, F here...(every struct has its own method different with others)
and i want create those type in one place:
create(typeName string) interface {
switch typeName {
case A:
return &A{}
case B:
return &B{}
....(more case than 10 times)
}
}
Then i use the create() here:
model := create("A")
now, model is type of interface, and no A`s fileds, how would simply to recover the type of model to A
Here is a sample of how you can use type assertion to convert interfaces to underlying structs
Here e is of the struct type and hence you can access any of its fields or struct methods.
package main
import (
"fmt"
)
type A struct {
AF int
}
type B struct {
BF string
}
func main() {
ds := []interface{}{
A{1},
B{"foo"},
}
for _, d := range ds {
switch e := d.(type) {
case A:
fmt.Println(e.AF)
case B:
fmt.Println(e.BF)
}
}
}
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?
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)
}
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.
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.
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
}
when I run the code snippet bellow, it raise a error
a.test undefined (type interface {} is interface with no methods)
It seem the type switch does not take effect.
package main
import (
"fmt"
)
type A struct {
a int
}
func(this *A) test(){
fmt.Println(this)
}
type B struct {
A
}
func main() {
var foo interface{}
foo = A{}
switch a := foo.(type){
case B, A:
a.test()
}
}
If I change it to
switch a := foo.(type){
case A:
a.test()
}
it's ok now.
This is normal behaviour that is defined by the spec (emphasis mine):
The TypeSwitchGuard may include a short variable declaration. When that form is used, the variable is declared at the beginning of the implicit block in each clause. In clauses with a case listing exactly one type, the variable has that type; otherwise, the variable has the type of the expression in the TypeSwitchGuard.
So, in fact, the type switch does take effect, but the variable a keeps the type interface{}.
One way you could get around this is to assert that foo has the method test(), which would look something like this:
package main
import (
"fmt"
)
type A struct {
a int
}
func (this *A) test() {
fmt.Println(this)
}
type B struct {
A
}
type tester interface {
test()
}
func main() {
var foo interface{}
foo = &B{}
if a, ok := foo.(tester); ok {
fmt.Println("foo has test() method")
a.test()
}
}
I try to convert interface{} to struct person...
package main
import (
"encoding/json"
"fmt"
)
func FromJson(jsonSrc string) interface{} {
var obj interface{}
json.Unmarshal([]byte(jsonSrc), &obj)
return obj
}
func main() {
type person struct {
Name string
Age int
}
json := `{"Name": "James", "Age": 22}`
actualInterface := FromJson(json)
fmt.Println("actualInterface")
fmt.Println(actualInterface)
var actual person
actual = actualInterface // error fires here -------------------------------
// -------------- type assertion always gives me 'not ok'
// actual, ok := actualInterface.(person)
// if ok {
// fmt.Println("actual")
// fmt.Println(actual)
// } else {
// fmt.Println("not ok")
// fmt.Println(actual)
// }
}
... But got error:
cannot use type interface {} as type person in assignment: need type assertion
To solve this error I tried to use type assertion actual, ok := actualInterface.(person) but always got not ok.
Playground link
The usual way to handle this is to pass a pointer to the output value to your decoding helper function. This avoids type assertions in your application code.
package main
import (
"encoding/json"
"fmt"
)
func FromJson(jsonSrc string, v interface{}) error {
return json.Unmarshal([]byte(jsonSrc), v)
}
func main() {
type person struct {
Name string
Age int
}
json := `{"Name": "James", "Age": 22}`
var p person
err := FromJson(json, &p)
fmt.Println(err)
fmt.Println(p)
}
Your problem is that you're creating an empty interface to begin with, and telling json.Unmarshal to unmarshal into it. While you've defined a person type, json.Unmarshal has no way of knowing that that's what you intend the type of the JSON to be. To fix this, move the definition of person to the top level (that is, move it out of the body of main), and changeFromJson` to this:
func FromJson(jsonSrc string) interface{} {
var obj person{}
json.Unmarshal([]byte(jsonSrc), &obj)
return obj
}
Now, when you return obj, the interface{} that's returned has person as its underlying type. You can run this code on the Go Playground.
By the way, your code is a bit un-idiomatic. I left the original Playground link unmodified except for my corrections so that it wouldn't be needlessly confusing. If you're curious, here's a version that's cleaned up to be more idiomatic (including comments on why I made the changes I did).