Golang:不同结构体类型之间的转换是否可能?

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

Golang : Is conversion between different struct types possible?

问题

假设我有两个类似的类型设置如下:

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

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

谢谢。

英文:

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.

答案1

得分: 95

给出对OneOfOne答案的参考,请参阅规范中的转换部分。

规范中指出:

> 非常量值 x 可以在以下任何情况下转换为类型 T
>
> * x 可以赋值给 T
> * x 的类型和 T 具有相同的基础类型。
> * x 的类型和 T 都是未命名的指针类型,并且它们的指针基类型具有相同的基础类型。
> * x 的类型和 T 都是整数或浮点数类型。
> * x 的类型和 T 都是复数类型。
> * x 是整数或字节或符文的切片,而 T 是字符串类型。
> * x 是字符串,而 T 是字节或符文的切片。

第一个被突出显示的情况适用于你的情况。两种类型都具有基础类型

[]struct { Field1 string Field2 int }

基础类型的定义如下:

> 如果 T 是预声明的布尔、数值或字符串类型,或者是类型字面量,则相应的基础类型就是 T 本身。否则,T 的基础类型是 T 在其类型声明中引用的类型的基础类型。(规范,类型

你使用了一个类型字面量来定义你的类型,所以这个类型字面量就是你的基础类型。

英文:

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.

答案2

得分: 92

对于你的具体示例,你可以轻松地将其转换为 playground 上的代码:

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

For your specific example, you can easily convert it <kbd>playground</kbd>:

t1 := type1{{&quot;A&quot;, 1}, {&quot;B&quot;, 2}}
t2 := type2(t1)
fmt.Println(t2)

答案3

得分: 22

从Go 1.8版本开始,在将一个结构体类型的值转换为另一个结构体类型时,会忽略结构体标签。在该版本中,无论结构体标签如何,类型type1和type2都可以相互转换。详见:https://beta.golang.org/doc/go1.8#language

英文:

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

答案4

得分: 16

Nicolas,在你的后续评论中,你说你在结构体上使用了字段标签;这些标签被视为定义的一部分,所以下面定义的t1和t2是不同的,你不能将t2强制转换为t1:

type t1 struct {
    Field1 string
}

type t2 struct {
    Field1 string `json:"field_1"`
}

更新:从Go 1.8开始,这个情况已经不再成立。

英文:

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:&quot;field_1&quot;`
}

UPDATE: This is no longer true as of Go 1.8

答案5

得分: 8

这不是标准的方法,但如果你希望以一种灵活的方式将一个结构体转换为一个map,或者如果你想要在不使用json:"-"的情况下去除结构体的某些属性,你可以使用JSON marshal。

具体来说,我会这样做:

type originalStruct []struct {
    Field1 string
    Field2 int
}

targetStruct := make(map[string]interface{}) // `targetStruct`可以是你选择的任何类型

temporaryVariable, _ := json.Marshal(originalStruct)
err = json.Unmarshal(temporaryVariable, &targetStruct) 
if err != nil {
    // 根据需要捕获异常并处理
}

这可能看起来像是一个技巧,但在我的大多数任务中非常有用。

英文:

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, &amp;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.

答案6

得分: 6

对于已经支持泛型的go v1.18,基本上我只需要创建一个接受任意类型参数并将其转换为另一种类型的方法,使用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
}

假设我有一个名为models.CreateUserRequest的结构体,我想将其转换为models.User。注意json标签必须相同

// 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"`
}

我可以像这样使用上面的utils方法:

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(reflect.TypeOf(data)) // 将输出 *models.User
log.Println(data)
英文:

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(&amp;data)
    if err != nil {
	  return nil, err
    }
    err = json.Unmarshal(b, &amp;result)
    if err != nil {
	  return nil, err
    }
    return &amp;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:&quot;name,omitempty&quot;`
   RegisterEmail    string `json:&quot;email,omitempty&quot;`
}

// models.User
type User struct {
   Name     string `json:&quot;name,omitempty&quot;`
   Email    string `json:&quot;email,omitempty&quot;`
   Phone    string `json:&quot;phone,omitempty&quot;`
}

I can use that utils method above like this

user := models.CreateUserRequest {
    Name: &quot;John Doe&quot;,
    Email: &quot;johndoe@gmail.com&quot;
}
data, err := utils.TypeConverter[models.User](&amp;user)
if err != nil {
    log.Println(err.Error())
}
log.Println(reflrect.TypeOf(data)) // will output *models.User
log.Println(data)

答案7

得分: 4

你可以手动使用一个映射函数,将类型为t1的每个元素映射到类型为t2的元素。这样可以实现。

func GetT2FromT1(ob1 *t1) *t2 {
    ob2 := &t2{Field1: ob1.Field1}
    return ob2
}
英文:

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 := &amp;t2 { Field1: t1.Field1, }
     return ob2
}

答案8

得分: 0

Agniswar Bakshi的答案如果您可以手动编写这些转换,那么速度更快、效果更好,但这里对Furqan Rahamath的答案进行了扩展。(在Golang playground上有一个更完整的示例

func Recast(a, b interface{}) error {
    js, err := json.Marshal(a)
    if err != nil {
        return err
    }
    return json.Unmarshal(js, b)
}

// 使用方法:

type User struct {
    Name string
    PasswordHash string
}

// 在提供用户之前删除PasswordHash:
type PrivateOutgoingUser struct {
    Name string
}

u1 := &User{Name: "Alice", PasswordHash: "argon2...."}
u2 := &PrivateOutgoingUser{}
err = Recast(u1, u2)
if err != nil {
    log.Panic("将u1重新转换为u2时出错", err)
}
log.Println("有限用户:", u2)

这里还有另一种使用JSON标记的方法,它更快,因为它不需要额外的编组-解组步骤,但不够灵活:

type User struct {
    Name string
    PasswordHash string `json:"-"`
}

user := &User{Name: "Tommy Tester", PasswordHash: "argon2...."}
js, err := json.Marshal(user)
log.Println("有限用户:", string(user))
英文:

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 := &amp;User{Name: &quot;Alice&quot;, PasswordHash: &quot;argon2....&quot;}
u2 := &amp;PrivateOutgoingUser{}
err = Recast(u1, u2)
if err != nil {
    log.Panic(&quot;Error recasting u1 to u2&quot;, err)
}
log.Println(&quot;Limited user:&quot;, 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:&quot;-&quot;` // - removes the field with JSON
}

user := &amp;User{Name: &quot;Tommy Tester&quot;, PasswordHash: &quot;argon2....&quot;}
js, err := json.Marshal(user)
log.Println(&quot;Limited user:&quot;, string(user))

huangapple
  • 本文由 发表于 2014年7月7日 22:40:38
  • 转载请务必保留本文链接:https://go.coder-hub.com/24613271.html
匿名

发表评论

匿名网友

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

确定