英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论