复制两个结构体之间的所有公共字段

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

Go- Copy all common fields between structs

问题

我有一个存储JSON的数据库,以及一个提供外部API的服务器,通过HTTP post可以更改数据库中的值。数据库被内部的不同进程使用,并且具有共同的命名方案。

客户看到的键是不同的,但与数据库中的键一一对应(存在未公开的键)。例如:

数据库中的内容如下:

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

向客户呈现的内容如下:

{ "num_bits": 8 }

API的字段名称可能会发生变化,但数据库的键始终保持一致。

我在结构体中使用相同的字段名称,但使用不同的标志来进行JSON编码:

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

我使用encoding/json进行Marshal/Unmarshal操作。

reflect是合适的工具吗?由于所有的键都是相同的,是否有更简单的方法?我在想是否可以使用某种形式的memcpy(如果我保持用户字段的相同顺序)。

英文:

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

答案1

得分: 11

无法在这里使用结构嵌入吗?

package main

import (
	"fmt"
)

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

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

func main() {
	db := DB{User{10}, true}
	fmt.Printf("Hello, DB: %+v\n", db)
	fmt.Printf("Hello, DB.NumBits: %+v\n", db.NumBits)
	fmt.Printf("Hello, User: %+v\n", db.User)
}
英文:

Couldn't struct embedding be useful here?

package main

import (
	"fmt"
)

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

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

func main() {
	db := DB{User{10}, true}
	fmt.Printf("Hello, DB: %+v\n", db)
	fmt.Printf("Hello, DB.NumBits: %+v\n", db.NumBits)
	fmt.Printf("Hello, User: %+v\n", db.User)
}

http://play.golang.org/p/9s4bii3tQ2

答案2

得分: 10

buf := bytes.Buffer{}
err := gob.NewEncoder(&buf).Encode(&DbVar)
if err != nil {
return err
}
u := User{}
err = gob.NewDecoder(&buf).Decode(&u)
if err != nil {
return err
}

英文:
buf := bytes.Buffer{}
err := gob.NewEncoder(&buf).Encode(&DbVar)
if err != nil {
    return err
}
u := User{}
err = gob.NewDecoder(&buf).Decode(&u)
if err != nil {
    return err
}

答案3

得分: 8

这是一个使用反射的解决方案。如果您需要更复杂的嵌套结构和嵌套结构字段等,您需要进一步开发它。

package main

import (
    "encoding/json"
    "fmt"
    "reflect"
)

type M map[string]interface{} // 仅为别名

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

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

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

func main() {
    d := new(DB)
    e := json.Unmarshal(Record, d)
    if e != nil {
        panic(e)
    }
    m := mapFields(d)
    fmt.Println("Mapped fields: ", m)
    u := new(User)
    o := applyMap(u, m)
    fmt.Println("Applied map: ", o)
    j, e := json.Marshal(o)
    if e != nil {
        panic(e)
    }
    fmt.Println("Output JSON: ", string(j))
}

func applyMap(u *User, m M) M {
    t := reflect.TypeOf(u).Elem()
    o := make(M)
    for i := 0; i < t.NumField(); i++ {
        f := t.FieldByIndex([]int{i})
        // 跳过未导出的字段
        if f.PkgPath != "" {
            continue
        }
        if x, ok := m[f.Name]; ok {
            k := f.Tag.Get("json")
            o[k] = x
        }
    }
    return o
}

func mapFields(x *DB) M {
    o := make(M)
    v := reflect.ValueOf(x).Elem()
    t := v.Type()
    for i := 0; i < v.NumField(); i++ {
        f := t.FieldByIndex([]int{i})
        // 跳过未导出的字段
        if f.PkgPath != "" {
            continue
        }
        o[f.Name] = v.FieldByIndex([]int{i}).Interface()
    }
    return o
}
英文:

Here's a solution using reflection. You have to further develop it if you need more complex structures with embedded struct fields and such.

http://play.golang.org/p/iTaDgsdSaI

package main

import (
	&quot;encoding/json&quot;
	&quot;fmt&quot;
	&quot;reflect&quot;
)

type M map[string]interface{} // just an alias

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

type DB struct {
	NumBits int  `json:&quot;bit_size&quot;`
	Secret  bool `json:&quot;secret_key&quot;`
}

type User struct {
	NumBits int `json:&quot;num_bits&quot;`
}

func main() {
	d := new(DB)
	e := json.Unmarshal(Record, d)
	if e != nil {
		panic(e)
	}
	m := mapFields(d)
	fmt.Println(&quot;Mapped fields: &quot;, m)
	u := new(User)
	o := applyMap(u, m)
	fmt.Println(&quot;Applied map: &quot;, o)
	j, e := json.Marshal(o)
	if e != nil {
		panic(e)
	}
	fmt.Println(&quot;Output JSON: &quot;, string(j))
}

func applyMap(u *User, m M) M {
	t := reflect.TypeOf(u).Elem()
	o := make(M)
	for i := 0; i &lt; t.NumField(); i++ {
		f := t.FieldByIndex([]int{i})
		// skip unexported fields
		if f.PkgPath != &quot;&quot; {
			continue
		}
		if x, ok := m[f.Name]; ok {
			k := f.Tag.Get(&quot;json&quot;)
			o[k] = x
		}
	}
	return o
}

func mapFields(x *DB) M {
	o := make(M)
	v := reflect.ValueOf(x).Elem()
	t := v.Type()
	for i := 0; i &lt; v.NumField(); i++ {
		f := t.FieldByIndex([]int{i})
		// skip unexported fields
		if f.PkgPath != &quot;&quot; {
			continue
		}
		o[f.Name] = v.FieldByIndex([]int{i}).Interface()
	}
	return o
}

答案4

得分: 2

使用结构标签,以下内容将非常好:

package main

import (
    "fmt"
    "log"

    "hacked/json"
)

var dbj = `{ "bit_size": 8, "secret_key": false }`

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

func main() {
    fmt.Println(dbj)
    // 从完整的数据库记录解组到User结构体
    var u User
    if err := json.Unmarshal([]byte(dbj), &u); err != nil {
        log.Fatal(err)
    }
    // 使用api字段名称重新组合User结构体
    api, err := json.MarshalTag(u, "api")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(string(api))
}

只需对encode.go进行小修补即可添加MarshalTag:

106c106,112
<       e := &encodeState{}
---
>       return MarshalTag(v, "json")
> }
> 
> // MarshalTag类似于Marshal,但是使用指定的标签键而不是默认的"json"。
> func MarshalTag(v interface{}, tag string) ([]byte, error) {
>       e := &encodeState{tagKey: tag}
201a208
>       tagKey       string
328c335
<               for _, ef := range encodeFields(v.Type()) {
---
>               for _, ef := range encodeFields(v.Type(), e.tagKey) {
509c516
< func encodeFields(t reflect.Type) []encodeField {
---
> func encodeFields(t reflect.Type, tagKey string) []encodeField {
540c547
<               tv := f.Tag.Get("json")
---
>               tv := f.Tag.Get(tagKey)
英文:

Using struct tags, the following would sure be nice,

package main

import (
    &quot;fmt&quot;
    &quot;log&quot;

    &quot;hacked/json&quot;
)

var dbj = `{ &quot;bit_size&quot;: 8, &quot;secret_key&quot;: false }`

type User struct {
    NumBits int `json:&quot;bit_size&quot; api:&quot;num_bits&quot;`
}

func main() {
    fmt.Println(dbj)
    // unmarshal from full db record to User struct
    var u User
    if err := json.Unmarshal([]byte(dbj), &amp;u); err != nil {
        log.Fatal(err)
    }
    // remarshal User struct using api field names 
    api, err := json.MarshalTag(u, &quot;api&quot;)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(string(api))
}

Adding MarshalTag requires just a small patch to encode.go:

106c106,112
&lt;       e := &amp;encodeState{}
---
&gt;       return MarshalTag(v, &quot;json&quot;)
&gt; }
&gt; 
&gt; // MarshalTag is like Marshal but marshalls fields with
&gt; // the specified tag key instead of the default &quot;json&quot;.
&gt; func MarshalTag(v interface{}, tag string) ([]byte, error) {
&gt;       e := &amp;encodeState{tagKey: tag}
201a208
&gt;       tagKey       string
328c335
&lt;               for _, ef := range encodeFields(v.Type()) {
---
&gt;               for _, ef := range encodeFields(v.Type(), e.tagKey) {
509c516
&lt; func encodeFields(t reflect.Type) []encodeField {
---
&gt; func encodeFields(t reflect.Type, tagKey string) []encodeField {
540c547
&lt;               tv := f.Tag.Get(&quot;json&quot;)
---
&gt;               tv := f.Tag.Get(tagKey)

答案5

得分: 2

以下是翻译好的部分:

// CopyCommonFields函数使用反射在两个结构体之间复制字段。如果src字段和dest字段具有相同的字段名,则将src字段复制到dest字段。
// Dest和src必须是结构体的指针。
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),
			)
		}
	}
}

使用方法:

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)
	// 输出:{A:foo}
}
英文:

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 &lt; destType.NumField(); i++ {
		destFieldsMap[destType.Field(i).Name] = i
	}

	for i := 0; i &lt; 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: &quot;foo&quot;,
		B: 5,
	}

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

答案6

得分: 1

你可以将结构体转换为具有相同字段名称和类型的结构体,从而重新分配字段标签:

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

英文:

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

package main

import &quot;encoding/json&quot;

type DB struct {
	dbNumBits
	Secret bool `json:&quot;secret_key&quot;`
}

type dbNumBits struct {
	NumBits int `json:&quot;bit_size&quot;`
}

type User struct {
	NumBits int `json:&quot;num_bits&quot;`
}

var Record = []byte(`{ &quot;bit_size&quot;: 8, &quot;secret_key&quot;: 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

答案7

得分: 0

这是一个没有使用反射、不安全或每个结构体一个函数的解决方案。示例有点复杂,也许你不需要完全像这样做,但关键是使用map[string]interface{}来摆脱带有字段标签的结构体。你可以在类似的解决方案中使用这个想法。

package main

import (
    "encoding/json"
    "fmt"
    "log"
)

// 完整的数据库记录示例
var dbj = `{ "bit_size": 8, "secret_key": false }`

// User类型只有要传递给API的字段
type User struct {
    // 标签仍然指定内部名称,而不是API名称
    NumBits int `json:"bit_size"`
}

// 内部字段名称到API字段名称的映射。
// (你可以有多个映射,甚至在运行时构建这个映射)
var ApiField = map[string]string{
    // 内部: API
    "bit_size": "num_bits",
    // ...
}

func main() {
    fmt.Println(dbj)
    // 通过解组将用户字段从完整的数据库记录中选择出来
    var u User
    if err := json.Unmarshal([]byte(dbj), &u); err != nil {
        log.Fatal(err)
    }
    // 从User结构体重新编组为json
    exportable, err := json.Marshal(u)
    if err != nil {
        log.Fatal(err)
    }
    // 这次解组为一个map,以摆脱字段标签。
    type jmap map[string]interface{}
    mInternal := jmap{}
    if err := json.Unmarshal(exportable, &mInternal); err != nil {
        log.Fatal(err)
    }
    // 翻译字段名称
    mExportable := jmap{}
    for internalField, v := range mInternal {
        mExportable[ApiField[internalField]] = v
    }
    // 使用API字段名称进行最终结果的编组
    if exportable, err = json.Marshal(mExportable); err != nil {
        log.Fatal(err)
    }
    fmt.Println(string(exportable))
}

输出:

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

编辑:更多解释。正如Tom在评论中指出的,代码背后有反射。这里的目标是通过使用库的可用功能来保持代码简单。Package json目前提供了两种处理数据的方式,结构体标签和map[string]interface{}。结构体标签允许你选择字段,但强制你静态选择一个json字段名称。映射允许你在运行时选择字段名称,但不能选择要编组的字段。如果json包同时允许你做这两件事就好了,但它不允许。这个答案只是展示了这两种技术以及如何将它们组合在OP中的示例问题的解决方案中。

英文:

Here's a solution without reflection, unsafe, or a function per struct. The example is a little convoluted, and maybe you wouldn't need to do it just like this, but the key is using a map[string]interface{} to get away from a struct with field tags. You might be able to use the idea in a similar solution.

package main

import (
    &quot;encoding/json&quot;
    &quot;fmt&quot;
    &quot;log&quot;
)

// example full database record
var dbj = `{ &quot;bit_size&quot;: 8, &quot;secret_key&quot;: false }`

// User type has only the fields going to the API
type User struct {
    // tag still specifies internal name, not API name
    NumBits int `json:&quot;bit_size&quot;`
}

// mapping from internal field names to API field names.
// (you could have more than one mapping, or even construct this
// at run time)
var ApiField = map[string]string{
    // internal: API
    &quot;bit_size&quot;: &quot;num_bits&quot;,
    // ...
}

func main() {
    fmt.Println(dbj)
    // select user fields from full db record by unmarshalling
    var u User
    if err := json.Unmarshal([]byte(dbj), &amp;u); err != nil {
        log.Fatal(err)
    }
    // remarshal from User struct back to json
    exportable, err := json.Marshal(u)
    if err != nil {
        log.Fatal(err)
    }
    // unmarshal into a map this time, to shrug field tags.
    type jmap map[string]interface{}
    mInternal := jmap{}
    if err := json.Unmarshal(exportable, &amp;mInternal); err != nil {
        log.Fatal(err)
    }
    // translate field names
    mExportable := jmap{}
    for internalField, v := range mInternal {
        mExportable[ApiField[internalField]] = v
    }
    // marshal final result with API field names
    if exportable, err = json.Marshal(mExportable); err != nil {
        log.Fatal(err)
    }
    fmt.Println(string(exportable))
}

Output:

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

Edit: More explanation. As Tom notes in a comment, there's reflection going on behind the code. The goal here is to keep the code simple by using the available capabilities of the library. Package json currently offers two ways to work with data, struct tags and maps of [string]interface{}. The struct tags let you select fields, but force you to statically pick a single json field name. The maps let you pick field names at run time, but not which fields to Marshal. It would be nice if the json package let you do both at once, but it doesn't. The answer here just shows the two techniques and how they can be composed in a solution to the example problem in the OP.

答案8

得分: 0

package main

import (
"encoding/json"
"fmt"
"log"
)

var dbj = { "bit_size": 8, "secret_key": false }

// translation from internal field name to api field name
type apiTrans struct {
db, api string
}

var User = []apiTrans{
{db: "bit_size", api: "num_bits"},
}

func main() {
fmt.Println(dbj)
type jmap map[string]interface{}
// unmarshal full db record
mdb := jmap{}
if err := json.Unmarshal([]byte(dbj), &mdb); err != nil {
log.Fatal(err)
}
// build result
mres := jmap{}
for _, t := range User {
if v, ok := mdb[t.db]; ok {
mres[t.api] = v
}
}
// marshal result
exportable, err := json.Marshal(mres)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(exportable))
}

英文:

"Is reflect the right tool for this?" A better question might be, "Are struct tags the right tool for this?" and the answer might be no.

package main

import (
    &quot;encoding/json&quot;
    &quot;fmt&quot;
    &quot;log&quot;
)

var dbj = `{ &quot;bit_size&quot;: 8, &quot;secret_key&quot;: false }`

// translation from internal field name to api field name
type apiTrans struct {
    db, api string
}

var User = []apiTrans{
    {db: &quot;bit_size&quot;, api: &quot;num_bits&quot;},
}

func main() {
    fmt.Println(dbj)
    type jmap map[string]interface{}
    // unmarshal full db record
    mdb := jmap{}
    if err := json.Unmarshal([]byte(dbj), &amp;mdb); err != nil {
        log.Fatal(err)
    }
    // build result
    mres := jmap{}
    for _, t := range User {
        if v, ok := mdb[t.db]; ok {
            mres[t.api] = v
        }
    }
    // marshal result
    exportable, err := json.Marshal(mres)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(string(exportable))
}

答案9

得分: 0

实现您的目标的一种高效方法是使用gob包

这里有一个使用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)
}

这是官方博客文章:https://blog.golang.org/gob

英文:

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

Here an example with the playground:

package main

import (
	&quot;bytes&quot;
	&quot;encoding/gob&quot;
	&quot;fmt&quot;
)

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(&amp;buf).Encode(&amp;db)
	if err != nil {
		panic(err)
	}

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

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

huangapple
  • 本文由 发表于 2012年7月18日 01:43:06
  • 转载请务必保留本文链接:https://go.coder-hub.com/11527935.html
匿名

发表评论

匿名网友

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

确定