2012-07-17 9 views
7

Tengo una base de datos que almacena JSON, y un servidor que proporciona una API externa a través de la cual a través de una publicación HTTP, los valores en esta base de datos se pueden cambiar. La base de datos es utilizada internamente por diferentes procesos, y como tal tienen un esquema de nomenclatura común.Go- Copie todos los campos comunes entre las estructuras

Las claves que el cliente ve son diferentes, pero correlaciona 1: 1 con las claves de la base de datos (hay claves no expuestas). Por ejemplo:

Esto está en la base de datos:

{ "bit_size": 8, "secret_key": false } 

Y esto se presenta al cliente:

{ "num_bits": 8 } 

el API puede cambiar con respecto a los nombres de campo, pero la base de datos siempre tiene llaves consistentes.

he nombrado los campos de la misma en la estructura, con diferentes banderas al codificador JSON:

type DB struct { 
    NumBits int `json:"bit_size"` 
    Secret bool `json:"secret_key"` 
} 
type User struct { 
    NumBits int `json:"num_bits"` 
} 

estoy usando encoding/json para hacer el Mariscal/unmarshal.

¿Es reflect la herramienta adecuada para esto? ¿Hay una manera más fácil ya que todas las teclas son iguales? Estaba pensando en algún tipo de memcpy (si mantuve los campos del usuario en el mismo orden).

+0

¿Qué estás tratando de lograr? Me parece que ya tengo la solución allí, tal vez sin darme cuenta. Agregue un método 'func (db DB) GetUser() Usuario {return User {NumBits: db.NumBit}}' y listo. Creo que también debería echarle un vistazo a las interfaces para proteger los parámetros internos y verificar la interfaz de Marshaler en encoding/json. De todos modos, siempre es mejor no usar reflejo. – Philip

+0

@Philip: tengo más de unas pocas estructuras con más de unos pocos campos cada una, por lo que me gustaría poder hacerlo todo con una función en lugar de una función por estructura. Si no hay un método más simple, puedo crear una función por estructura. – tjameson

+1

solo vaya con la reflexión, no es tan caro como podría pensar – thwd

Respuesta

4

Aquí hay una solución usando la reflexión. Debe desarrollarlo aún más si necesita estructuras más complejas con campos de estructuras incrustadas y demás.

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 
} 
+0

Creo que me estoy inclinando por esta opción. ¿Hay alguna manera de tener la misma estructura Marshal de forma diferente? Lo dudo ... – tjameson

+0

por favor, defina de manera diferente ... – thwd

+0

Creo que estoy buscando esto (la respuesta de Sonia): http://stackoverflow.com/a/11548141/538551. Creo que intentaré sugerir esta corrección en go-nuts. Por ahora, sin embargo, esto es lo que decidí hacer. – tjameson

0

Aquí hay una solución sin reflexión, insegura o una función por estructura. El ejemplo es un poco intrincado, y tal vez no tendrías que hacerlo así, pero la clave es usar un mapa [string] interface {} para alejarte de una estructura con etiquetas de campo. Es posible que pueda usar la idea en una solución similar.

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

de salida:

{ "bit_size": 8, "SECRET_KEY": false} {
"num_bits": 8}

Edición: Más explicación. Como Tom nota en un comentario, hay una reflexión detrás del código. El objetivo aquí es mantener el código simple mediante el uso de las capacidades disponibles de la biblioteca. Package json actualmente ofrece dos formas de trabajar con datos, struct tags y mapas de [string] interface {}. Las etiquetas struct le permiten seleccionar campos, pero le obligan a elegir de forma estática un solo nombre de campo json. Los mapas le permiten elegir los nombres de los campos en tiempo de ejecución, pero no los campos para Marshal. Sería bueno si el paquete json le permite hacer ambas cosas a la vez, pero no es así. La respuesta aquí solo muestra las dos técnicas y cómo se pueden componer en una solución al problema de ejemplo en el PO.

+3

su intento de evitar el uso de la reflexión provocó el uso indirecto del doble de reflexión de lo que sería necesario si lo hiciera de forma directa. – thwd

+0

Difícil de decir sin ver el código para una solución "directa". – Sonia

+0

Aquí tienes: http://play.golang.org/p/iTaDgsdSaI – thwd

0

"es reflejar la herramienta adecuada para esto?" Una mejor pregunta podría ser: "¿Son las etiquetas struct las herramientas adecuadas para esto?" y la respuesta podría ser no.

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

¿Por qué las etiquetas struct no serían la herramienta adecuada para esto? ¿Cuándo son apropiadas las etiquetas struct? – tjameson

+0

Porque son parte de la definición de tipo estático. No puede cambiarlos para exportar json con diferentes nombres de campo. Estructuras similares con etiquetas diferentes son tipos diferentes y no asignables o convertibles por las reglas de tipo estricto de Go. (Este es el punto en el que quería memcpy, pero tampoco funciona en Go). Y el paquete de codificación/json está codificado de forma rígida para la etiqueta struct "json". En conjunto, la codificación/json Go + existente no tiene la flexibilidad que deseas con las etiquetas struct por sí solas. Eso lleva a soluciones usando mapas, pirateo de codificación/json, etc. – Sonia

+0

Sí, como su versión de codificación/json. Podría simplemente rodar mi propia biblioteca json con soporte para etiquetas dinámicas, pero preferiría intentar usar la biblioteca estándar si no es demasiado trabajo (para mantenimiento). – tjameson

2

uso de etiquetas de estructura, lo siguiente sería seguro de ser agradable,

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

Adición MarshalTag requiere sólo un pequeño parche para 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) 
+0

Hrm ... entonces tendría que rodar mi propia versión ... ¿Crees que los desarrolladores de Golang permitirían tal cambio? Esto podría ser muy útil para ocasiones en las que la misma API se expone de manera diferente a diferentes solicitantes, sin demasiado cambio de código. Aunque me gusta la idea general ... – tjameson

+0

No cambian apresuradamente la biblioteca estándar. Pensé que esta pregunta era interesante y que otros podrían haber encontrado algo similar, pero realmente no tengo ni idea. El lugar donde flotar la idea es la [lista de go-nuts] (http://groups.google.com/group/golang-nuts). – Sonia

3

No se pudo incorporar struct sea útil aquí?

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

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