2013-08-21 54 views
3

我正在編寫一個將JSON解碼爲結構的Go庫。 JSON有一個相當簡單的公共模式,但我希望這個庫的使用者能夠將附加字段解碼到自己的結構中,以嵌入公共結構,從而避免使用地圖。理想情況下,我想只解碼JSON一次。如何將JSON解組到另一段代碼提供的Go結構中?

目前看起來像這樣。 (爲簡潔,刪除錯誤處理。)

的JSON:

{ "CommonField": "foo", 
    "Url": "http://example.com", 
    "Name": "Wolf" } 

庫代碼:

// The base JSON request. 
type BaseRequest struct { 
    CommonField string 
} 

type AllocateFn func() interface{} 
type HandlerFn func(interface{}) 

type Service struct { 
    allocator AllocateFn 
    handler HandlerFn 
} 

func (Service *s) someHandler(data []byte) { 
    v := s.allocator() 
    json.Unmarshal(data, &v) 
    s.handler(v) 
} 

的應用代碼:

// The extended JSON request 
type MyRequest struct { 
    BaseRequest 
    Url string 
    Name string 
} 

func allocator() interface{} { 
    return &MyRequest{} 
} 

func handler(v interface{}) { 
    fmt.Printf("%+v\n", v); 
} 

func main() { 
    s := &Service{allocator, handler} 
    // Run s, eventually s.someHandler() is called 
} 

我沒有的事喜歡這個設置就是allocator功能。所有的實現都將返回一個新的「子類型」BaseRequest。在更動態的語言中,我將傳遞類型MyRequest代替,並在庫中實例化。 Go中有類似的選項嗎?

+0

作爲一個便箋,我建議不要爲了簡潔而刪除錯誤,因爲人們會以您的問題爲例來看看。這是一個很好的機會提醒人們,錯誤很重要。 –

回答

2

有幾種方法可以解決這個問題。一個既簡單又方便的想法是定義一個更豐富的請求類型,而不是傳遞原始類型。通過這種方式,您可以以友好的方式實現默認行爲,並支持邊緣情況。這也可以避免在自定義類型中嵌入默認類型,並且允許您在不中斷客戶端的情況下擴展功能。

爲了尋找靈感:

type Request struct { 
    CommonField string 

    rawJSON []byte 
} 

func (r *Request) Unmarshal(value interface{}) error { 
    return json.Unmarshal(r.rawJSON, value) 
} 

func handler(req *Request) { 
    // Use common data. 
    fmt.Println(req.CommonField) 

    // If necessary, poke into the underlying message. 
    var myValue MyType 
    err := req.Unmarshal(&myValue) 
    // ... 
} 

func main() { 
    service := NewService(handler) 
    // ... 
} 
+0

在你的例子中,req.CommonField是如何被填充的?我會兩次解組JSON嗎?一次用於普通領域,一次用於「子類型」? –

+0

在處理程序被調用之前,它會被服務設置。作爲一個整體的邏輯可能不得不針對不同類型不止一次地調用unmarshal。我不會爲此擔心。嘗試計時,如果你想知道。 –

1

我認爲json.RawMessage是用來延遲解碼JSON的子集。在你的情況下,你可以做這樣的事情:

package main 

import (
↦  "encoding/json" 
↦  "fmt" 
) 

type BaseRequest struct { 
↦  CommonField string 
↦  AppData json.RawMessage 
} 

type AppData struct { 
↦  AppField string 
} 

var someJson string = ` 
{ 
↦  "CommonField": "foo", 
↦  "AppData": { 
↦  ↦  "AppField": "bar" 
↦  } 
} 
` 

func main() { 
↦  var baseRequest BaseRequest 
↦  if err := json.Unmarshal([]byte(someJson), &baseRequest); err != nil { 
↦  ↦  panic(err) 
↦  } 

↦  fmt.Println("Parsed BaseRequest", baseRequest) 

↦  var appData AppData 
↦  if err := json.Unmarshal(baseRequest.AppData, &appData); err != nil { 
↦  ↦  panic(err) 
↦  } 

↦  fmt.Println("Parsed AppData", appData) 
} 
+0

我想讓這些字段與CommonField相同。即,{「CommonField」:「foo」,「AppField」:「bar」} –

0

我想出了另一種方式是使用反射。

調整我原來的示例中,庫代碼變爲:

// The base JSON request. 
type BaseRequest struct { 
    CommonField string 
} 

type HandlerFn func(interface{}) 

type Service struct { 
    typ reflect.Type 
    handler HandlerFn 
} 

func (Service *s) someHandler(data []byte) { 
    v := reflect.New(s.typ).Interface() 
    json.Unmarshal(data, &v) 
    s.handler(v) 
} 

和應用程序代碼變成:

// The extended JSON request 
type MyRequest struct { 
    BaseRequest 
    Url string 
    Name string 
} 

func handler(v interface{}) { 
    fmt.Printf("%+v\n", v); 
} 

func main() { 
    s := &Service{reflect.TypeOf(MyRequest{}), handler} 
    // Run s, eventually s.someHandler() is called 
} 

,如果我喜歡這個更好我還沒有決定。也許要走的路只是簡單地解組數據兩次。

+0

您最終在應用程序代碼中分配MyRequest,所以也許您不妨將它傳遞給庫,而不是傳遞類型並在庫中重新分配一個類型?或者,如果你真的想避免在應用程序中分配,你可以反映。TypeOf((* MyRequest)(nil))。Elem()(非常醜陋)。 –

相關問題