26

JSONを格納するデータベースと、HTTPPOSTを介してこのデータベースの値を変更できる外部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いました(ユーザーフィールドを同じ順序で保持した場合)。

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

これは、リフレクション、安全でない、または構造体ごとの関数のないソリューションです。この例は少し複雑で、このようにする必要はないかもしれませんが、重要なのは、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の問題例のソリューションでそれらをどのように構成できるかを示しています。

于 2012-07-18T01:55:18.367 に答える
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

次の関数はreflectを使用して、2つの構造体間でフィールドをコピーします。同じフィールド名を持つ場合、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 に答える