How to unmarshal JSON with a generic interface as a field

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

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要解组成哪种具体类型。包无法为你完成这个任务。

假设TypeATypeB被定义为:

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.

huangapple
  • 本文由 发表于 2023年3月30日 07:02:28
  • 转载请务必保留本文链接:https://go.coder-hub.com/75882863.html
匿名

发表评论

匿名网友

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

确定