'Golang : Is conversion between different struct types possible?
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.
Solution 1:[1]
For your specific example, you can easily convert it playground:
t1 := type1{{"A", 1}, {"B", 2}}
t2 := type2(t1)
fmt.Println(t2)
Solution 2:[2]
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 typeT
in any of these cases:
x
is assignable toT
.x
's type andT
have identical underlying types.x
's type andT
are unnamed pointer types and their pointer base types have identical underlying types.x
's type andT
are both integer or floating point types.x
's type andT
are both complex types.x
is an integer or a slice of bytes or runes andT
is a string type.x
is a string andT
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 isT
itself. Otherwise,T
's underlying type is the underlying type of the type to whichT
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 3:[3]
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:[4]
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:[5]
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:[6]
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:[7]
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)
Solution 8:[8]
Agniswar Bakshi's answer is faster and better if you can write those conversions manually, but here's another quick-and-dirty way to do it.
A more complete example is available at https://play.golang.com/p/oNH9RA1HiGm
func Recast(a, b interface{}) error {
js, err := json.Marshal(a)
if err != nil {
return err
}
return json.Unmarshal(js, b)
}
// Usage:
type User struct {
Name string
PasswordHash string
}
// remove PasswordHash before providing user:
type PrivateOutgoingUser struct {
Name string
}
u1 := &User{Name: "Alice", PasswordHash: "argon2...."}
u2 := &PrivateOutgoingUser{}
err = Recast(u1, u2)
if err != nil {
log.Panic("Error recasting u1 to u2", err)
}
log.Println("Limited user:", u2)
Here's another way that uses JSON tagging which is faster, since it doesn't require that extra marshal-unmarshal step, but not quite as flexible:
type User struct {
Name string
PasswordHash string `json:"-"` // - removes the field with JSON
}
user := &User{Name: "Tommy Tester", PasswordHash: "argon2...."}
js, err := json.Marshal(user)
log.Println("Limited user:", string(user))
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | OneOfOne |
Solution 2 | nemo |
Solution 3 | Ludi Rehak |
Solution 4 | BMiner |
Solution 5 | Agniswar Bakshi |
Solution 6 | Furqan Rahamath |
Solution 7 | wahyudotdev |
Solution 8 |