如何将JSON解组为由不同代码提供的Go结构?

时间:2013-08-21 14:32:30

标签: json go unmarshalling

我正在编写一个将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“子类型”。在更动态的语言中,我会改为传递MyRequest的类型,并在库中实例化。我在Go中有类似的选择吗?

3 个答案:

答案 0 :(得分:2)

有几种方法可以解决这个问题。一个既简单又方便的想法是定义一个更丰富的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)
    // ...
}

答案 1 :(得分: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)
}

答案 2 :(得分: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
}

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