如何将JSON反序列化为由其他代码提供的Go结构体?

huangapple go评论89阅读模式
英文:

How do I unmarshal JSON into a Go struct provided by a different piece of code?

问题

我正在编写一个Go库,用于将JSON解码为结构体。JSON具有相对简单的通用模式,但我希望该库的使用者能够将额外的字段解码到自己的结构体中,而不需要使用映射。理想情况下,我希望只解码一次JSON。

目前的代码大致如下(为简洁起见,省略了错误处理部分):

JSON数据:

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

库代码:

// 基本的JSON请求
type BaseRequest struct {
    CommonField string
}

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

type Service struct {
    allocator AllocateFn
    handler HandlerFn
}   

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

应用代码:

// 扩展的JSON请求
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}
    // 运行s,最终会调用s.someHandler()
}

我不喜欢这个设置的一点是allocator函数。所有的实现都只会返回一个新的BaseRequest的"子类型"。在更动态的语言中,我会传递MyRequest的类型,并在库内部实例化。在Go中是否有类似的选项呢?

英文:

I am writing a Go library that will decode JSON into a struct. The JSON has a fairly simple common schema, but I want consumers of this library to be able to decode additional fields into their own structs that embed the common struct, avoiding the need to use maps. Ideally, I'd like to decode the JSON only once.

Currently it looks something like this. (Error handling removed for brevity.)

The JSON:

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

The library code:

// 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 app code:

// 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
}

The thing I don't like about this setup is the allocator function. All implementations are simply going to return a new BaseRequest "sub-type". In a more dynamic language I would pass the type of MyRequest in instead, and instantiate inside the library. Do I have a similar option in Go?

答案1

得分: 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)
}

这段代码使用了json.RawMessage来延迟解码JSON的子集。在BaseRequest结构体中,AppData字段的类型被定义为json.RawMessage,这样可以将未解码的JSON数据存储在该字段中。然后,你可以使用json.Unmarshal函数将AppData字段的值解码到AppData结构体中。

英文:

I think json.RawMessage is used to delay decoding subsets of JSON. In your case you can maybe do something like this:

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

得分: 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) {
    // 使用公共数据。
    fmt.Println(req.CommonField)

    // 如果需要,访问底层消息。
    var myValue MyType
    err := req.Unmarshal(&myValue)
    // ...
}

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

希望这能给你提供一些灵感。

英文:

There are several ways to handle this. One idea which is both simple and convenient is defining a richer Request type that you provide to the handler, instead of handing off the raw type. This way you can implement the default behavior in a friendly way, and support the edge cases. This would also avoid the need to embed the default type on custom types, and allow you to expand functionality without breaking clients.

For inspiration:

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)
    // ...
}

答案3

得分: 0

我想到的另一种方法是使用反射。

调整我的原始示例,库代码如下:

// 基本的 JSON 请求。
type BaseRequest struct {
    CommonField string
}

type HandlerFn func(interface{})

type Service struct {
    typ reflect.Type
    handler HandlerFn
}   

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

应用代码如下:

// 扩展的 JSON 请求
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}
    // 运行 s,最终会调用 s.someHandler()
}

我还没有决定是否更喜欢这种方法。也许直接解析数据两次才是更好的方法。

英文:

Another way I came up with is to use reflection.

Tweaking my original example, the library code becomes:

// 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)
}

and the app code becomes:

// 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
}

I haven't decided if I like this any better. Maybe the way to go is just to simply unmarshal the data twice.

huangapple
  • 本文由 发表于 2013年8月21日 22:32:29
  • 转载请务必保留本文链接:https://go.coder-hub.com/18360296.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定