26

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

客户看到的key是不同的,但是和数据库中的key是1:1映射的(有未暴露的key)。例如:

这是在数据库中:

{ "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用来做元帅/元帅。

reflect正确的工具吗?有没有更简单的方法,因为所有的键都是一样的?我在想某种memcpy(如果我保持用户字段的顺序相同)。

4

9 回答 9

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

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

于 2014-12-15T19:54:08.547 回答
9
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
}
于 2017-02-22T21:46:47.063 回答
8

这是使用反射的解决方案。如果您需要具有嵌入式结构字段等的更复杂的结构,则必须进一步开发它。

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

package main

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

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

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})
        // skip unexported fields
        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})
        // skip unexported fields
        if f.PkgPath != "" {
            continue
        }
        o[f.Name] = v.FieldByIndex([]int{i}).Interface()
    }
    return o
}
于 2012-07-18T15:25:53.393 回答
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)
    // unmarshal from full db record to User struct
    var u User
    if err := json.Unmarshal([]byte(dbj), &u); err != nil {
        log.Fatal(err)
    }
    // remarshal User struct using api field names 
    api, err := json.MarshalTag(u, "api")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(string(api))
}

添加 MarshalTag 只需要对 encode.go 进行一个小补丁:

106c106,112
<       e := &encodeState{}
---
>       return MarshalTag(v, "json")
> }
> 
> // MarshalTag is like Marshal but marshalls fields with
> // the specified tag key instead of the default "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)
于 2012-07-18T18:38:43.740 回答
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

于 2018-12-03T22:30:40.883 回答
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))
}
于 2012-07-18T16:59:27.667 回答
0

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

package main

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

// example full database record
var dbj = `{ "bit_size": 8, "secret_key": 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:"bit_size"`
}

// 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
    "bit_size": "num_bits",
    // ...
}

func main() {
    fmt.Println(dbj)
    // select user fields from full db record by unmarshalling
    var u User
    if err := json.Unmarshal([]byte(dbj), &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, &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))
}

输出:

{“bit_size”:8,“secret_key”:假}
{“num_bits”:8}

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

于 2012-07-18T01:55:18.367 回答
0

以下函数使用反射在两个结构之间复制字段。如果 src 字段具有相同的字段名称,则将它们复制到 dest 字段。

// 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),
            )
        }
    }
}

用法:

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}
}
于 2021-06-28T11:30:15.333 回答
-1

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

这是操场的一个例子:

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

于 2020-09-10T20:47:05.090 回答