英文:
How to unmarshal JSON with a generic interface as a field
问题
我有一个通用的Response对象,具有以下结构:
type Response struct {
Data Data `json:"data"`
Error string `json:"error,omitempty"`
NextPageToken string `json:"next_page_token,omitempty"`
}
Data
类型是一个接口,有许多实现(比如PingResponse等)。如何将Response
反序列化为其底层类型?下面是完整的示例,它总是触发错误error: json: cannot unmarshal object into Go struct field Response.data of type main.Data
:
type Response struct {
Data Data `json:"data"`
Error string `json:"error,omitempty"`
NextPageToken string `json:"next_page_token,omitempty"`
}
type Data interface {
Foo()
}
type TypeA struct {
Field1 string `json:"field1"`
Field2 int `json:"field2"`
}
func (a *TypeA) Foo() {}
type TypeB struct {
Field3 float64 `json:"field3"`
}
func (b *TypeB) Foo() {}
func main() {
jsonStr := `{
"data": {
"field1": "some string",
"field2": 123
},
"error": "",
"next_page_token": ""
}`
var response Response
err := json.Unmarshal([]byte(jsonStr), &response)
if err != nil {
fmt.Println("error:", err)
return
}
switch data := response.Data.(type) {
case *TypeA:
fmt.Println("TypeA:", data.Field1, data.Field2)
case *TypeB:
fmt.Println("TypeB:", data.Field3)
default:
fmt.Println("Unknown type")
}
}
英文:
I have a generic Response object with the following struct:
type Response struct {
Data Data `json:"data"`
Error string `json:"error,omitempty"`
NextPageToken string `json:"next_page_token,omitempty"`
}
The Data
type is an interface, with many implementations (think PingResponse, etc). How do I unmarshal the Response
into its underlying type? The full example is below, which always triggers the error error: json: cannot unmarshal object into Go struct field Response.data of type main.Data
:
type Response struct {
Data Data `json:"data"`
Error string `json:"error,omitempty"`
NextPageToken string `json:"next_page_token,omitempty"`
}
type Data interface{
Foo()
}
type TypeA struct {
Field1 string `json:"field1"`
Field2 int `json:"field2"`
}
func (a *TypeA) Foo() {}
type TypeB struct {
Field3 float64 `json:"field3"`
}
func (b *TypeB) Foo() {}
func main() {
jsonStr := `{
"data": {
"field1": "some string",
"field2": 123
},
"error": "",
"next_page_token": ""
}`
var response Response
err := json.Unmarshal([]byte(jsonStr), &response)
if err != nil {
fmt.Println("error:", err)
return
}
switch data := response.Data.(type) {
case *TypeA:
fmt.Println("TypeA:", data.Field1, data.Field2)
case *TypeB:
fmt.Println("TypeB:", data.Field3)
default:
fmt.Println("Unknown type")
}
}
答案1
得分: 5
你必须告诉encoding/json
要解组成哪种具体类型。包无法为你完成这个任务。
假设TypeA
和TypeB
被定义为:
type TypeA struct {
FieldA string `json:"field"`
}
type TypeB struct {
FieldB string `json:"field"`
}
在这种情况下,无法确定要解组成哪种类型。
关于你的示例,我们可以这样告诉encoding/json
要解组成哪种类型:
- var response Response
+ response := Response{Data: &TypeA{}}
如果事先不知道类型,可以将其编组为map[string]interface{}
:
type Response struct {
Data map[string]interface{} `json:"data"`
Error string `json:"error,omitempty"`
NextPageToken string `json:"next_page_token,omitempty"`
}
然后可以像这样确定类型:
if field1, ok := response.Data["field1"]; ok {
fmt.Println("TypeA:", field1, response.Data["field2"])
} else {
if field3, ok := response.Data["field3"]; ok {
fmt.Println("TypeB:", field3)
} else {
fmt.Println("未知类型")
}
}
另一种解决方案是在JSON中嵌入类型信息:
jsonStr := `{
"data": {
"field1": "some string",
"field2": 123
},
+ "type": "A",
"error": "",
"next_page_token": ""
}`
type Response struct {
Data json.RawMessage `json:"data"`
Type string `json:"type"`
Error string `json:"error,omitempty"`
NextPageToken string `json:"next_page_token,omitempty"`
}
然后根据response.Type
的值解码response.Data
。请参考encoding/json
提供的示例:https://pkg.go.dev/encoding/json#example-RawMessage-Unmarshal。
英文:
You have to tell encoding/json
which concrete type to unmarshal to. The package can not do it for you.
Let's assume that TypeA
and TypeB
are defined as:
type TypeA struct {
FieldA string `json:"field"`
}
type TypeB struct {
FieldB string `json:"field"`
}
There is no way to decide which type to unmarshal to in this case.
Regarding your example, we can tell encoding/json
which type to unmarshal to like this:
- var response Response
+ response := Response{Data: &TypeA{}}
If you don't know the type beforehand, you can marshal it into map[string]interface{}
:
type Response struct {
- Data Data `json:"data"`
+ Data map[string]interface{} `json:"data"`
Error string `json:"error,omitempty"`
NextPageToken string `json:"next_page_token,omitempty"`
}
And determine the type like this:
if field1, ok := response.Data["field1"]; ok {
fmt.Println("TypeA:", field1, response.Data["field2"])
} else {
if field3, ok := response.Data["field3"]; ok {
fmt.Println("TypeB:", field3)
} else {
fmt.Println("Unknown type")
}
}
Another solution is to embed the type information in the json:
jsonStr := `{
"data": {
"field1": "some string",
"field2": 123
},
+ type": "A",
"error": "",
"next_page_token": ""
}`
type Response struct {
- Data Data `json:"data"`
+ Data json.RawMessage `json:"data"`
+ Type string `json:"type"`
Error string `json:"error,omitempty"`
NextPageToken string `json:"next_page_token,omitempty"`
}
And then decode response.Data
according to the value of response.Type
. See the example provided by encoding/json
: https://pkg.go.dev/encoding/json#example-RawMessage-Unmarshal.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论