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)
}
используя теги 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))
}