4

我正在编写一个将 JSON 解码为结构的 Go 库。JSON 有一个相当简单的通用模式,但我希望这个库的使用者能够将其他字段解码到他们自己的嵌入通用结构的结构中,从而避免使用映射。理想情况下,我只想解码 JSON 一次。

目前它看起来像这样。(为简洁起见,删除了错误处理。)

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

我认为 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 回答
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)
    // ...
}
于 2013-08-21T15:34:47.143 回答
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
}

我还没有决定我是否更喜欢这个。也许要走的路只是简单地将数据解组两次。

于 2013-08-21T20:28:25.947 回答