web-dev-qa-db-ja.com

Go-構造体間のすべての共通フィールドをコピーします

JSONを格納するデータベースと、HTTP POSTを介してこのデータベースの値を変更できる外部APIを提供するサーバーがあります。データベースは内部でさまざまなプロセスによって使用されるため、共通の命名スキームがあります。

顧客に表示されるキーは異なりますが、データベース内のキーと1:1でマップします(公開されていないキーがあります)。例えば:

これはデータベースにあります:

{ "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を考えていました(ユーザーフィールドを同じ順序に保った場合)。

18
beatgammit

これがリフレクションを使用した解決策です。構造体フィールドなどが埋め込まれたより複雑な構造が必要な場合は、さらに開発する必要があります。

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
}
7
thwd
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
}
7
user1212212

ここで構造体の埋め込みは役に立ちませんでしたか?

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

7
Dfr

構造体タグを使用すると、次のようになります。

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)
1
Sonia

同じフィールド名とタイプの構造をキャストして、フィールドタグを効果的に再割り当てできます。

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

0
vearutop

「これに適したツールを反映していますか?」より良い質問は、「構造体タグはこれに適したツールですか?」です。答えはノーかもしれません。

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

これは、リフレクション、安全でない、または構造体ごとの関数のないソリューションです。この例は少し複雑で、このようにする必要はないかもしれませんが、重要なのは、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":false}
{"num_bits":8}

編集:より多くの説明。トムがコメントで指摘しているように、コードの背後で反省が起こっています。ここでの目標は、ライブラリの利用可能な機能を使用してコードを単純に保つことです。パッケージjsonは現在、データを操作する2つの方法、構造体タグと[string] interface {}のマップを提供しています。 structタグを使用するとフィールドを選択できますが、静的に1つのjsonフィールド名を選択する必要があります。マップを使用すると、実行時にフィールド名を選択できますが、マーシャルするフィールドは選択できません。 jsonパッケージで両方を同時に実行できると便利ですが、そうではありません。ここでの答えは、2つの手法と、OPの問題例のソリューションでそれらをどのように構成できるかを示しています。

0
Sonia