2012-07-17 13 views
7

Ho un database che memorizza JSON e un server che fornisce un'API esterna per cui tramite un post HTTP, i valori in questo database possono essere modificati. Il database viene utilizzato internamente da diversi processi e, come tale, ha uno schema di denominazione comune.Go- Copia tutti i campi comuni tra le strutture

Le chiavi che il cliente vede sono diverse, ma mappa 1: 1 con le chiavi nel database (ci sono le chiavi non esposte). Per esempio:

Questo è nel database:

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

E questo è presentato al cliente:

{ "num_bits": 8 } 

L'API può cambiare per quanto riguarda i nomi dei campi, ma il database è sempre chiavi coerenti.

ho chiamato i campi lo stesso nel struct, con diverse bandiere al codificatore JSON:

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

sto usando encoding/json per fare il maresciallo/Unmarshal.

reflect è lo strumento giusto per questo? C'è un modo più semplice poiché tutti i tasti sono uguali? Stavo pensando ad una specie di memcpy (se ho mantenuto i campi utente nello stesso ordine).

+0

Cosa stai cercando di realizzare? Mi sembra che la soluzione sia già lì, forse senza rendersene conto. Aggiungi un metodo 'func (db DB) GetUser() Utente {return User {NumBits: db.NumBit}}' e il gioco è fatto. Penso che dovresti anche dare un'occhiata alle interfacce per schermare i parametri interni e controllare l'interfaccia Marshaler nella codifica/json. Ad ogni modo, è sempre meglio non usare la riflessione. – Philip

+0

@Philip - Ho più di una struttura con più di alcuni campi, quindi mi piacerebbe essere in grado di fare tutto con una funzione invece di una funzione per struttura. Se non esiste un metodo più semplice, posso comunque creare una funzione per struct. – tjameson

+1

basta andare con la riflessione, non è così costoso come si potrebbe pensare – thwd

risposta

4

Ecco una soluzione che utilizza la riflessione. Devi svilupparlo ulteriormente se hai bisogno di strutture più complesse con campi struct incorporati e simili.

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

Penso che mi sto appoggiando a questa opzione. C'è un modo per avere lo stesso struct marshal in modo diverso? Ne dubito ... – tjameson

+0

si prega di definire in modo diverso ... – thwd

+0

Penso che sto cercando questo (la risposta di Sonia): http://stackoverflow.com/a/11548141/538551. Penso che proverò a suggerire questa soluzione su go-nuts. Per ora, tuttavia, questo è quello che ho deciso di fare. – tjameson

0

Ecco una soluzione senza riflessione, non sicura o una funzione per struttura. L'esempio è un po 'complicato, e forse non avresti bisogno di farlo proprio così, ma la chiave sta usando una map [string] interface {} per uscire da una struttura con tag di campo. Potresti essere in grado di utilizzare l'idea in una soluzione simile.

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

uscita:

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

Edit: Altro spiegazione. Come nota Tom in un commento, c'è una riflessione in corso dietro al codice. L'obiettivo qui è quello di mantenere il codice semplice utilizzando le funzionalità disponibili della libreria. Il pacchetto json attualmente offre due modi per lavorare con dati, tag struct e mappe dell'interfaccia [string] {}. I tag struct ti permettono di selezionare i campi, ma ti costringono a selezionare staticamente un singolo nome di campo json. Le mappe ti consentono di selezionare i nomi dei campi in fase di esecuzione, ma non i campi su Maresciallo. Sarebbe bello se il pacchetto json ti permettesse di farlo entrambi contemporaneamente, ma non è così. La risposta qui mostra solo le due tecniche e come possono essere composte in una soluzione al problema di esempio nell'OP.

+3

il tentativo di evitare l'uso del riflesso ha comportato l'utilizzo indiretto del doppio del riflesso rispetto a quello che sarebbe stato necessario se lo si faceva direttamente. – thwd

+0

Difficile dire senza vedere il codice per una soluzione "diretta". – Sonia

+0

Ecco qui: http://play.golang.org/p/iTaDgsdSaI – thwd

0

"È riflettono lo strumento giusto per questo?" Una domanda migliore potrebbe essere: "I tag struct sono lo strumento giusto per questo?" e la risposta potrebbe essere 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

Perché i tag struct non dovrebbero essere lo strumento giusto per questo? Quando sono appropriati i tag struct? – tjameson

+0

Perché fanno parte della definizione del tipo statico. Non è possibile cambiarli per esportare JSON con nomi di campi diversi. Le strutture simili con tag diversi sono di diverso tipo e non sono assegnabili o convertibili secondo le rigide regole di tipo di Go. (Questo è il punto in cui si desidera memcpy, ma ciò non funziona bene neanche in Go.) E il pacchetto encoding/json è hardcoded al tag struct "json". Tutti insieme, la codifica/json Go + esistente non ha la flessibilità che si desidera con i tag struct da soli. Ciò porta a soluzioni che utilizzano mappe, codifica hacking/json e così via. – Sonia

+0

Sì, come la tua versione di codifica/json. Potrei semplicemente pubblicare la mia libreria json con supporto per i tag dinamici, ma preferirei provare a utilizzare la libreria standard se non è troppo più lavoro (per la manutenzione). – tjameson

2

uso dei nomi struct, il seguente sarebbe certo bello,

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

Aggiunta MarshalTag richiede solo una piccola patch per 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 ... allora avrei dovuto lanciare la mia versione ... Pensi che gli sviluppatori di golang permetterebbero un simile cambiamento? Ciò potrebbe essere molto utile per le occasioni in cui la stessa API viene esposta in modo diverso a diversi richiedenti, senza troppe modifiche al codice. Mi piace l'idea generale però ... – tjameson

+0

Non cambiano in fretta la libreria standard. Pensavo che questa domanda fosse interessante e che altri avrebbero potuto incontrare qualcosa di simile, ma non ne ho idea. Il luogo in cui galleggiare l'idea è la [lista dei dadi] (http://groups.google.com/group/golang-nuts). – Sonia

3

Impossibile struct embedding essere utile qui?

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) 
Problemi correlati