4

JSON を構造体にデコードする Go ライブラリを作成しています。JSON にはかなり単純な共通スキーマがありますが、このライブラリの利用者が追加フィールドを共通構造体を埋め込んだ独自の構造体にデコードできるようにして、マップを使用する必要がないようにしたいと考えています。理想的には、JSON を 1 回だけデコードしたいと考えています。

現在はこんな感じです。(簡潔にするためにエラー処理を削除しました。)

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すべての実装は、単に新しい「サブタイプ」を返すだけです。より動的な言語では、代わりに in の型を渡しMyRequest、ライブラリ内でインスタンス化します。Goにも同様のオプションがありますか?

4

3 に答える 3

2

これを処理するにはいくつかの方法があります。シンプルで便利なアイデアの 1 つは、生の型を渡す代わりに、ハンドラーに提供するよりリッチな Request 型を定義することです。このようにして、デフォルトの動作をわかりやすい方法で実装し、エッジ ケースをサポートできます。これにより、カスタム タイプにデフォルト タイプを埋め込む必要がなくなり、クライアントを壊すことなく機能を拡張できます。

インスピレーションのために:

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)
    // ...
}
于 2013-08-21T15:34:47.143 に答える
2

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)
}
于 2013-08-21T14:45:22.093 に答える
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
}

私はこれがもっと好きかどうか決めていません。おそらく、データを 2 回アンマーシャリングするだけです。

于 2013-08-21T20:28:25.947 に答える