2012-07-17 30 views
7

我有一個存儲JSON的數據庫和一個通過HTTP post提供外部API的服務器,可以更改此數據庫中的值。數據庫由內部的不同進程使用,因此具有通用的命名方案。Go-複製結構之間的所有常見字段

顧客看到的鑰匙是不同的,但映射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(如果我保持用戶字段的順序相同)。

+0

什麼是你想實現什麼?在我看來,解決方案已經在那裏,也許沒有意識到。添加一個'func(db DB)GetUser()用戶{返回用戶{NumBits:db.NumBit}}'的方法,就完成了。我想你還應該看看接口來屏蔽內部參數,並檢查編碼/ json中的Marshaler接口。不管怎樣,不使用反射總是更好。 – Philip 2012-07-17 18:33:39

+0

@Philip - 我有一個以上的幾個結構,每個結構都有幾個字段,所以我希望能夠使用一個函數而不是每個結構的函數來完成。如果沒有簡單的方法,我可以爲每個結構創建一個函數。 – tjameson 2012-07-17 19:46:27

+1

只是反思,它不像你想象的那麼昂貴 – thwd 2012-07-18 07:55:58

回答

4

下面是使用反射的解決方案。如果你需要更復雜的嵌入結構域等結構,你必須進一步開發它。

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

我想我傾向於這個選項。有沒有辦法讓不同的結構元帥?我懷疑它... – tjameson 2012-07-18 19:28:16

+0

請定義不同... – thwd 2012-07-18 19:48:45

+0

我想我正在尋找這個(索尼婭的答案):http://stackoverflow.com/a/11548141/538551。我想我會試着把這個問題提出來。然而,現在,這是我決定要做的。 – tjameson 2012-07-25 08:00:26

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」:假}
{ 「num_bits」:8}

編輯:更多的解釋。正如Tom在評論中所指出的那樣,代碼背後有一些反思。這裏的目標是通過使用庫的可用功能來簡化代碼。包json目前提供了兩種處理[string] interface {}的數據,結構標籤和映射的方法。結構標籤可以讓你選擇字段,但強制你靜態選擇一個單一的json字段名稱。這些地圖讓你可以在運行時選擇字段名稱,但不能將哪些字段添加到Marshal。如果json包允許你同時做兩件事,那將會很好,但事實並非如此。這裏的答案只是顯示了兩種技術,以及如何將它們組合爲解決OP中的示例問題的方案。

+3

您嘗試避免直接使用反射會導致直接使用兩倍的間接反射,而不是直接反射。 – thwd 2012-07-18 07:35:45

+0

很難說沒有看到「簡單」解決方案的代碼。 – Sonia 2012-07-18 14:29:41

+0

這裏你去:http://play.golang.org/p/iTaDgsdSaI – thwd 2012-07-18 15:26:54

0

「是反映了這個正確的工具?」一個更好的問題可能是,「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)) 
} 
+0

爲什麼結構標籤不是正確的工具?什麼時候結構標籤合適? – tjameson 2012-07-18 18:32:26

+0

因爲它們是靜態類型定義的一部分。你不能改變它們來導出帶有不同字段名稱的json。具有不同標籤的類似結構是不同的類型,不能由Go的嚴格類型規則分配或轉換。 (這是你想要memcpy的地方,但在Go中也不適用)。而encoding/json包被硬編碼爲結構標籤「json」。總而言之,現有的Go +編碼/ json並不完全具有您想要的結構標籤的靈活性。這導致解決方案使用地圖,黑客編碼/ JSON等。 – Sonia 2012-07-18 18:50:18

+0

是的,就像你的編碼/ json版本一樣。我可以推出我自己的json庫以支持動態標籤,但如果它不是太多的工作(用於維護),我寧願嘗試使用標準庫。 – tjameson 2012-07-18 19:33:46

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

Hrm ...那麼我將不得不推出我自己的版本...你認爲golang開發者會允許這樣的改變嗎?對於不同的請求者使用相同的API的場合,不需要太多的代碼改變,這可能會非常有用。我喜歡這個總體思路...... – tjameson 2012-07-18 19:30:02

+0

他們不會倉促地改變標準庫。我認爲這個問題很有趣,其他人可能遇到類似的問題,但我真的不知道。浮現這個想法的地方是[go-nuts list](http://groups.google.com/group/golang-nuts)。 – Sonia 2012-07-18 19:42:31

3

無法嵌入結構是有用嗎?

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)