Go- Copy all common fields between structs

Viewed 22494

I have a database that stores JSON, and a server that provides an external API to whereby through an HTTP post, values in this database can be changed. The database is used by different processes internally, and as such have a common naming scheme.

The keys the customer sees are different, but map 1:1 with the keys in the database (there are unexposed keys). For example:

This is in the database:

{ "bit_size": 8, "secret_key": false }

And this is presented to the client:

{ "num_bits": 8 }

The API can change with respect to field names, but the database always has consistent keys.

I have named the fields the same in the struct, with different flags to the json encoder:

type DB struct {
    NumBits int  `json:"bit_size"`
    Secret  bool `json:"secret_key"`
}
type User struct {
    NumBits int `json:"num_bits"`
}

I'm using encoding/json to do the Marshal/Unmarshal.

Is reflect the right tool for this? Is there an easier way since all of the keys are the same? I was thinking some kind of memcpy (if I kept the user fields in the same order).

9 Answers

The following function use reflect to copy fields between two structs. A src field is copied to a dest field if they have the same field name.

// CopyCommonFields copies src fields into dest fields. A src field is copied 
// to a dest field if they have the same field name.
// Dest and src must be pointers to structs.
func CopyCommonFields(dest, src interface{}) {
    srcType := reflect.TypeOf(src).Elem()
    destType := reflect.TypeOf(dest).Elem()
    destFieldsMap := map[string]int{}

    for i := 0; i < destType.NumField(); i++ {
        destFieldsMap[destType.Field(i).Name] = i
    }

    for i := 0; i < srcType.NumField(); i++ {
        if j, ok := destFieldsMap[srcType.Field(i).Name]; ok {
            reflect.ValueOf(dest).Elem().Field(j).Set(
                reflect.ValueOf(src).Elem().Field(i),
            )
        }
    }
}

Usage:

func main() {
    type T struct {
        A string
        B int
    }

    type U struct {
        A string
    }

    src := T{
        A: "foo",
        B: 5,
    }

    dest := U{}
    CopyCommonFields(&dest, &src)
    fmt.Printf("%+v\n", dest)
    // output: {A:foo}
}

You can cast structures if they have same field names and types, effectively reassigning field tags:

package main

import "encoding/json"

type DB struct {
    dbNumBits
    Secret bool `json:"secret_key"`
}

type dbNumBits struct {
    NumBits int `json:"bit_size"`
}

type User struct {
    NumBits int `json:"num_bits"`
}

var Record = []byte(`{ "bit_size": 8, "secret_key": false }`)

func main() {
    d := new(DB)
    e := json.Unmarshal(Record, d)
    if e != nil {
        panic(e)
    }

    var u User = User(d.dbNumBits)
    println(u.NumBits)
}

https://play.golang.org/p/uX-IIgL-rjc

An efficient way to achieve your goal is to use the gob package.

Here an example with the playground:

package main

import (
    "bytes"
    "encoding/gob"
    "fmt"
)

type DB struct {
    NumBits int
    Secret  bool
}

type User struct {
    NumBits int
}

func main() {
    db := DB{10, true}
    user := User{}

    buf := bytes.Buffer{}
    err := gob.NewEncoder(&buf).Encode(&db)
    if err != nil {
        panic(err)
    }

    err = gob.NewDecoder(&buf).Decode(&user)
    if err != nil {
        panic(err)
    }
    fmt.Println(user)
}

Here the official blog post: https://blog.golang.org/gob

Related