如何处理将数据解组到一个自定义接口中,其类型只能在解组后确定。

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

How to handle unmarshaling to a custom interface whose type could only be determined after unmarshaling

问题

我有一个类似这样的 JSON 响应:

  1. {
  2. "foo" : "bar",
  3. "object" : {
  4. "type" : "action",
  5. "data" : "somedata"
  6. }
  7. }

这里的 object 可能是多种类型之一。我定义了这些类型并让它们实现了一个公共接口。

  1. type IObject interface {
  2. GetType() string
  3. }
  4. type Action struct {
  5. Type string `json:"type"`
  6. Data string `json:"data"`
  7. }
  8. func (a Action) GetType() string {
  9. return "action"
  10. }
  11. type Activity struct {
  12. Type string `json:"type"`
  13. Duration int `json:"duration"`
  14. }
  15. func (a Activity) GetType() string {
  16. return "activity"
  17. }

还有一个响应结构体:

  1. type Response struct {
  2. Foo string `json:"foo"`
  3. Object IObject `json:"object"`
  4. }

由于实现了 IObject 接口的结构体的类型信息包含在结构体内部,所以没有办法在不进行解组的情况下获得它。我也不能更改 JSON 响应的结构。目前,我正在使用自定义的解组器来处理这个问题:

  1. func UnmarshalObject(m map[string]interface{}, object *IObject) error {
  2. if m["type"] == "action" {
  3. b, err := json.Marshal(m)
  4. if err != nil {
  5. return err
  6. }
  7. action := Action{}
  8. if err = json.Unmarshal(b, &action); err != nil {
  9. return err
  10. }
  11. *object = action
  12. return nil
  13. }
  14. if m["type"] == "activity" {
  15. b, err := json.Marshal(m)
  16. if err != nil {
  17. return err
  18. }
  19. activity := Activity{}
  20. if err = json.Unmarshal(b, &activity); err != nil {
  21. return err
  22. }
  23. *object = activity
  24. return nil
  25. }
  26. return errors.New("unknown actor type")
  27. }
  28. func (r *Response) UnmarshalJSON(data []byte) error {
  29. raw := struct {
  30. Foo string `json:"foo"`
  31. Object interface{} `json:"object"`
  32. }{}
  33. err := json.Unmarshal(data, &raw)
  34. if err != nil {
  35. return err
  36. }
  37. r.Foo = raw.Foo
  38. if err = UnmarshalObject(raw.Object.(map[string]interface{}), &r.Object); err != nil {
  39. return err
  40. }
  41. return nil
  42. }

所以我做的基本上是:

  1. 将对象解组为 interface{}
  2. 类型转换为 map[string]interface{}
  3. 读取 "type" 值以确定类型
  4. 创建确定类型的新实例
  5. 转换回 JSON
  6. 再次解组为确定类型的新实例
  7. 将实例赋值给字段

这种方式让我感到不舒服,特别是来回进行的编组/解组。有没有更优雅的方法来解决这个问题?

英文:

I have a json response like this

  1. {
  2. "foo" : "bar",
  3. "object" : {
  4. "type" : "action",
  5. "data" : "somedata"
  6. }
  7. }

Here the object could be one of multiple types. I define the types and have them implement a common interface.

  1. type IObject interface {
  2. GetType() string
  3. }
  4. type Action struct {
  5. Type string `json:"type"`
  6. Data string `json:"data"`
  7. }
  8. func (a Action) GetType() string {
  9. return "action"
  10. }
  11. type Activity struct {
  12. Type string `json:"type"`
  13. Duration int `json:"duration"`
  14. }
  15. func (a Activity) GetType() string {
  16. return "activity"
  17. }

And a response struct

  1. type Response struct {
  2. Foo string `json:"foo"`
  3. Object IObject `json:"object"`
  4. }

As the type information of a struct that implements IObject is contained within the struct, there is no way to learn in without unmarshaling. I also cannot change the structure of the json response. Currently I am dealing with this problem using a custom unmarshaller:

  1. func UnmarshalObject(m map[string]interface{}, object *IObject) error {
  2. if m["type"] == "action" {
  3. b, err := json.Marshal(m)
  4. if err != nil {
  5. return err
  6. }
  7. action := Action{}
  8. if err = json.Unmarshal(b, &action); err != nil {
  9. return err
  10. }
  11. *object = action
  12. return nil
  13. }
  14. if m["type"] == "activity" {
  15. b, err := json.Marshal(m)
  16. if err != nil {
  17. return err
  18. }
  19. activity := Activity{}
  20. if err = json.Unmarshal(b, &activity); err != nil {
  21. return err
  22. }
  23. *object = activity
  24. return nil
  25. }
  26. return errors.New("unknown actor type")
  27. }
  28. func (r *Response) UnmarshalJSON(data []byte) error {
  29. raw := struct {
  30. Foo string `json:"foo"`
  31. Object interface{} `json:"object"`
  32. }{}
  33. err := json.Unmarshal(data, &raw)
  34. if err != nil {
  35. return err
  36. }
  37. r.Foo = raw.Foo
  38. if err = UnmarshalObject(raw.Object.(map[string]interface{}), &r.Object); err != nil
  39. {
  40. return err
  41. }
  42. return nil
  43. }

So what I do is basically

  1. Unmarshall the object into an interface{}
  2. Typecast to map[string]interface{}
  3. Read the "type" value to determine the type
  4. Create a new instance of the determined type
  5. Marshal back to json
  6. Unmarshal again to the new instance of the determined type
  7. Assign the instance to the field

This feels off and I am not comfortable with it. Especially the marshaling/unmarshaling back and forth. Is there a more elegant way to solve this problem?

答案1

得分: 1

你可以使用json.RawMessage

  1. func (r *Response) UnmarshalJSON(data []byte) error {
  2. var raw struct {
  3. Foo string `json:"foo"`
  4. Object json.RawMessage `json:"object"`
  5. }
  6. if err := json.Unmarshal(data, &raw); err != nil {
  7. return err
  8. }
  9. r.Foo = raw.Foo
  10. var obj struct {
  11. Type string `json:"type"`
  12. }
  13. if err := json.Unmarshal(raw.Object, &obj); err != nil {
  14. return err
  15. }
  16. switch obj.Type {
  17. case "action":
  18. r.Object = new(Action)
  19. case "activity":
  20. r.Object = new(Activity)
  21. }
  22. return json.Unmarshal(raw.Object, r.Object)
  23. }

https://go.dev/play/p/6dqiybS4zNp

英文:

You can use json.RawMessage.

  1. func (r *Response) UnmarshalJSON(data []byte) error {
  2. var raw struct {
  3. Foo string `json:"foo"`
  4. Object json.RawMessage `json:"object"`
  5. }
  6. if err := json.Unmarshal(data, &raw); err != nil {
  7. return err
  8. }
  9. r.Foo = raw.Foo
  10. var obj struct {
  11. Type string `json:"type"`
  12. }
  13. if err := json.Unmarshal(raw.Object, &obj); err != nil {
  14. return err
  15. }
  16. switch obj.Type {
  17. case "action":
  18. r.Object = new(Action)
  19. case "activity":
  20. r.Object = new(Activity)
  21. }
  22. return json.Unmarshal(raw.Object, r.Object)
  23. }

https://go.dev/play/p/6dqiybS4zNp

huangapple
  • 本文由 发表于 2022年8月14日 19:02:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/73351086.html
匿名

发表评论

匿名网友

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

确定