Golang: 可以在不同的结构类型之间进行转换吗?

假设我有两个类似的类型:

type type1 []struct {
Field1 string
Field2 int
}
type type2 []struct {
Field1 string
Field2 int
}

是否有一种直接的方法将值从 type1写到 type2,并且知道它们具有相同的字段? (除了编写一个循环将所有字段从源复制到目标)

谢谢。

137908 次浏览

For your specific example, you can easily convert it playground:

t1 := type1\{\{"A", 1}, {"B", 2}}
t2 := type2(t1)
fmt.Println(t2)

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.

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

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

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
}

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.

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: "johndoe@gmail.com"
}
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)

Agniswar Bakshi's answer is faster and better if you can write those conversions manually, but here's an expansion on Furqan Rahamath's answer. (A more complete example is available on the Golang playground )

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))