Golang : Is conversion between different struct types possible?
StructGoStruct Problem Overview
Let's say I have two similar types set this way :
type type1 []struct {
Field1 string
Field2 int
}
type type2 []struct {
Field1 string
Field2 int
}
Is there a direct way to write values from a type1 to a type2, knowing that they have the same fields ? (other than writing a loop that will copy all the fields from the source to the target)
Thanks.
Struct Solutions
Solution 1 - Struct
To give a reference to OneOfOne's answer, see the Conversions section of the spec.
It states that
> A non-constant value x
can be converted to type T
in any of these
> cases:
>
> * x
is assignable to T
.
> * x
's type and T
have identical underlying types.
> * x
's type and T
are unnamed pointer types and their pointer base types have identical underlying types.
> * x
's type and T
are both integer or floating point types.
> * x
's type and T
are both complex types.
> * x
is an integer or a slice of bytes or runes and T
is a string type.
> * x
is a string and T
is a slice of bytes or runes.
The first and highlighted case is your case. Both types have the underlying type
[]struct { Field1 string Field2 int }
An underlying type is defined as
> If T
is one of the predeclared boolean, numeric, or string types, or a type literal, the corresponding underlying type is T
itself. Otherwise, T
's underlying type is the underlying type of the type to which T
refers in its type declaration. (spec, Types)
You are using a type literal to define your type so this type literal is your underlying type.
Solution 2 - Struct
For your specific example, you can easily convert it playground:
t1 := type1{{"A", 1}, {"B", 2}}
t2 := type2(t1)
fmt.Println(t2)
Solution 3 - Struct
As of Go 1.8, struct tags are ignored when converting a value from one struct type to another. Types type1 and type2 will be convertible, regardless of their struct tags, in that Go release. https://beta.golang.org/doc/go1.8#language
Solution 4 - Struct
Nicolas, in your later comment you said you were using field tags on the struct; these count as part of definition, so t1 and t2 as defined below are different and you cannot cast t2(t1):
type t1 struct {
Field1 string
}
type t2 struct {
Field1 string `json:"field_1"`
}
UPDATE: This is no longer true as of Go 1.8
Solution 5 - Struct
You can manually use a mapper function which maps each element of type t1 to type t2. It will work.
func GetT2FromT1(ob1 *t1) *t2 {
ob2 := &t2 { Field1: t1.Field1, }
return ob2
}
Solution 6 - Struct
This is not the standard way, but if you wish to have a flexible approach to convert a struct to, lets say, a map, or if you want to get rid of some properties of your struct without using `json:"-", you can use JSON marshal.
Concretely, here is what I do:
type originalStruct []struct {
Field1 string
Field2 int
}
targetStruct := make(map[string]interface{}) // `targetStruct` can be anything of your choice
temporaryVariable, _ := json.Marshal(originalStruct)
err = json.Unmarshal(temporaryVariable, &targetStruct)
if err != nil {
// Catch the exception to handle it as per your need
}
Might seem like a hack, but was pretty useful in most of my tasks.
Solution 7 - Struct
for go v1.18 that already support generic, basically i just create method that accept any type of parameter and convert it to another type with json.Marshal / Unmarshal
// utils.TypeConverter
func TypeConverter[R any](data any) (*R, error) {
var result R
b, err := json.Marshal(&data)
if err != nil {
return nil, err
}
err = json.Unmarshal(b, &result)
if err != nil {
return nil, err
}
return &result, err
}
suppose that i have a struct called models.CreateUserRequest and i want to convert it to models.User. Note that json tag must be same
// models.CreateUserRequest
type CreateUserRequest struct {
Fullname string `json:"name,omitempty"`
RegisterEmail string `json:"email,omitempty"`
}
// models.User
type User struct {
Name string `json:"name,omitempty"`
Email string `json:"email,omitempty"`
Phone string `json:"phone,omitempty"`
}
I can use that utils method above like this
user := models.CreateUserRequest {
Name: "John Doe",
Email: "[email protected]"
}
data, err := utils.TypeConverter[models.User](&user)
if err != nil {
log.Println(err.Error())
}
log.Println(reflrect.TypeOf(data)) // will output *models.User
log.Println(data)