Go-копировать все общие поля между структурами

у меня есть база данных, которая хранит JSON, и сервер, который предоставляет внешний API, с помощью которого через HTTP post значения в этой базе данных могут быть изменены. База данных используется различными процессами внутри и как таковая имеет общую схему именования.

ключи, которые видит клиент, разные, но карта 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 выполнить Маршал/Демаршаллинга.

Is reflect правильный инструмент для этого? Есть ли более простой способ, так как все ключи одинаковы? Я думал о каком-то memcpy (если я сохранил поля пользователя в тот же приказ).

6 ответов


вот решение, использующее отражение. Вы должны продолжать развивать его, если вам нужны более сложные структуры со встроенными полями структуры и тому подобное.

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
}

не может ли вложение struct быть полезным здесь?

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


b := bytes.Buffer{}
gob.NewEncoder(&b).Encode(&DbVar)
u := User{}
gob.NewDecoder(&b).Decode(&u)

используя теги struct, было бы неплохо,

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 требует только небольшой патч для кодирования.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)

вот решение без отражения, небезопасное или функция на структуру. Пример немного запутан, и, возможно, Вам не нужно было бы делать это просто так, но ключ использует интерфейс map[string] {}, чтобы уйти от структуры с тегами полей. Возможно, вы сможете использовать эту идею в аналогичном решении.

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}

Edit: больше объяснений. Как отмечает том в комментарии,за кодом происходит отражение. Цель здесь состоит в том, чтобы сохранить код простым, используя доступные возможности библиотеки. В настоящее время пакет json предлагает два способа работы с данными, тегами структуры и картами интерфейса [string] {}. Теги структуры позволяют выбирать поля, но заставляют статически выбирать одно имя поля json. Карты позволяют выбирать имена полей во время выполнения, но не поля для Маршалирования. Было бы неплохо, если бы пакет json позволил вам это сделать и то и другое одновременно, но это не так. Ответ здесь просто показывает два метода и как они могут быть составлены в решении проблемы примера в OP.


" является ли reflect правильным инструментом для этого?"Лучший вопрос может быть", являются ли теги struct правильным инструментом для этого?- и ответ может быть отрицательным.

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