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