105

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.

Nicolas Marshall
  • 4,186
  • 9
  • 36
  • 54
  • 2
    Take a look at http://stackoverflow.com/questions/24383812/why-you-cant-use-a-type-from-a-different-package-if-it-has-the-same-signature/24384133 – rvignacio Jul 07 '14 at 14:50

8 Answers8

95

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.

nemo
  • 55,207
  • 13
  • 135
  • 135
  • This should be the selected answer, +1 for the reference. – OneOfOne Jul 07 '14 at 17:29
  • Thanks for both your answers. However i don't get why i still get the following error : `cannot convert res (type []struct { Name string "json:\"a.name\"" }) to type ListSociete` With types `type Societe struct {Name string}` and `type ListSociete []Societe` . I ran a `return ListSociete(res)`. Is it because of the json tag `a.name` ? – Nicolas Marshall Jul 07 '14 at 20:51
  • 3
    Post another question with more details, please. – OneOfOne Jul 07 '14 at 20:57
  • No problem. Here it is : http://stackoverflow.com/questions/24642148/golang-type-conversion-between-slices-of-structs – Nicolas Marshall Jul 08 '14 at 21:54
93

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

t1 := type1{{"A", 1}, {"B", 2}}
t2 := type2(t1)
fmt.Println(t2)
OneOfOne
  • 95,033
  • 20
  • 184
  • 185
  • 2
    While this is possible it's kinda nonsense. Essentially you're changing the type. If the structs do not match then the code throws an exception. – Richard May 20 '15 at 12:53
  • 49
    @Richard: No, if structs' types do not match, it won't panic, it just won't compile. – user Nov 22 '15 at 05:08
  • 20
    This is useful if the first struct is given by a lib, but you want to generate json or xml from the second one. – jumper Jun 27 '18 at 19:57
  • 1
    if type1 and type2 were `struct` with identical fields instead of `[]struct`, and I have a slice of type1 (`var mySlice []type1`), is there any way I can easily convert to `mySlice` to a `[]type2`? – sgonzalez Aug 03 '22 at 16:22
22

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

Ludi Rehak
  • 241
  • 2
  • 3
  • Note that struct tags in golang are the optional string literals after the field declarations. This terminology is different from, e. g., C usage, where the structure tag essentially gives the structure a name. – Alexander Klauer Aug 03 '17 at 07:58
16

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

BMiner
  • 16,669
  • 12
  • 53
  • 53
m.kocikowski
  • 5,422
  • 2
  • 23
  • 9
8

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.

Furqan Rahamath
  • 2,034
  • 1
  • 19
  • 29
  • I really like this solution! Could you (or someone) explain why it might be a hack? Because at first glance it appears to be simple and clean. – Carlo Nyte Jun 14 '22 at 10:12
  • Ideally, if your project is built right, using type casting or using `json: "-"` in your struct declarations would be much cleaner ways to accomplish struct conversion. But if you are in a position where you need more flexibility, then using maps as an intermediate state is helpful, which comes at the cost of additional lines and processing (as Marshal and Unmarshal are expensive). Please feel free to correct me or add any additional reasons. – Furqan Rahamath Jun 15 '22 at 16:10
6

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)
wahyudotdev
  • 81
  • 2
  • 4
  • You can just do this with interface{}, generics not needed. – fatal_error May 28 '22 at 13:52
  • It is useful when the properties of the struct are partially different, for example when the database models contains full user info while to create a new user some of the properties are not absolutely necessary. If using your mentioned method it won't compile because the properties are different, and also it's an unreasonable case since it's just for converting data types – wahyudotdev May 29 '22 at 14:32
  • Interfaces can actually do that, too (and it'll compile just fine), using the same JSON marshal/unmarshal trick that you're doing here. See https://stackoverflow.com/a/72436927/1301627 and https://play.golang.com/p/oNH9RA1HiGm for an example. – fatal_error May 30 '22 at 16:07
4

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
}
Agniswar Bakshi
  • 328
  • 5
  • 21
0

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))
fatal_error
  • 5,457
  • 2
  • 18
  • 18