dongyange1101 2017-07-02 13:33
浏览 452
已采纳

Golang Unmarshal切片类型接口

In this example, I would be trying to load 2D scenes that contain polygons. In the code, I would have many different structs such as Circle, Square, Rectangle, Pentagon, etc.. All would share common funcs, such as Area and Perimeter. The scene itself would be stored as a slice of the interface Polygon.

Here is the code I'm using to test this:

package main

import (
    "encoding/json"
    "fmt"
    "math"
)

type Polygon interface {
    Area() float32
}

type Rectangle struct {
    Base   float32 `json:"base"`
    Height float32 `json:"height"`
    X      float32 `json:"x"`
    Y      float32 `json:"y"`
}

func (r *Rectangle) Area() float32 {
    return r.Base * r.Height
}

type Circle struct {
    Radius float32 `json:"radius"`
    X      float32 `json:"x"`
    Y      float32 `json:"y"`
}

func (c *Circle) Area() float32 {
    return c.Radius * c.Radius * math.Pi
}

func main() {
    rect := Rectangle{Base: 10, Height: 10, X: 10, Y: 10}
    circ := Circle{Radius: 10, X: 0, Y: 0}

    sliceOfPolygons := make([]Polygon, 0, 2)

    sliceOfPolygons = append(sliceOfPolygons, &rect, &circ)

    jsonData, err := json.Marshal(sliceOfPolygons)
    if err != nil {
        panic(err)
    }

    fmt.Println(string(jsonData))

    newSlice := make([]Polygon, 0)

    err = json.Unmarshal(jsonData, &newSlice)
    if err != nil {
        panic(err)
    }
}

In this example, I setup a slice of 2 polygons, marshal it and then try to unmarshal it again. The marshal-ed string is:

[{"base":10,"height":10,"x":10,"y":10},{"radius":10,"x":0,"y":0}]

But when I try to Unmarshal it panics:

panic: json: cannot unmarshal object into Go value of type main.Polygon

If this worked it would be really useful and simple to use. I'd say that Unmarshall can't distinguish between a Rectangle and a Circle from the json string so it can't possibly know what struct to build.

Is there any way to tag the struct or tell Unmarshal how to distinguish this structs?

  • 写回答

1条回答 默认 最新

  • dopuzf0898 2017-07-02 14:33
    关注

    Thought that way to distinguish whether the json is Circle or Rectangle. In your JSON, there is no identify for the struct which can detect kind of objects. So let's make rules.

    • Rectangle have base and height both greater than 0
    • Circle have radius greater than 0

    To unmarshal JSON, it should have commonly fields like below.

    type Object struct {
        Base   float32 `json:"base,omitempty"`
        Radius float32 `json:"radius,omitempty"`
        Height float32 `json:"height,omitempty"`
        X      float32 `json:"x"`
        Y      float32 `json:"y"`
    }
    

    This struct can be stored Rectangle or Circle both. Then, add method IsCircle and IsRectangle.

    func (obj *Object) IsCircle() bool {
        return obj.Radius > 0
    }
    
    func (obj *Object) IsRectangle() bool {
        return obj.Base > 0 && obj.Height > 0
    }
    

    You can make method like Kind() to return identity of struct instead. As you think best. Finally, you should add ToCircle/ToRectangle methods.

    func (obj *Object) ToCircle() *Circle {
        return &Circle{
            Radius: obj.Radius,
            X:      obj.X,
            Y:      obj.Y,
        }
    }
    
    func (obj *Object) ToRectangle() *Rectangle {
        return &Rectangle{
            Base:   obj.Base,
            Height: obj.Height,
            X:      obj.X,
            Y:      obj.Y,
        }
    }
    

    If you want slice of Polygon interface, you should convert this slice of Object to the slice of Polygon like below.

    var polygons []Polygon
    for _, obj := range newSlice {
        if obj.IsCircle() {
            polygons = append(polygons, obj.ToCircle())
        } else if obj.IsRectangle() {
            polygons = append(polygons, obj.ToRectangle())
        }
    }
    

    https://play.golang.org/p/kO_F4GTYdA

    UPDATE

    One another approach. Make converters which convert from map[string]interface{}. The converter can detect the struct with looking fields exists.

    var converters = []func(map[string]interface{}) Polygon{
        func(m map[string]interface{}) Polygon {
            rectangle := new(Rectangle)
            if base, ok := m["base"]; ok {
                rectangle.Base = toFloat32(base)
            } else {
                return nil
            }
            if height, ok := m["height"]; ok {
                rectangle.Height = toFloat32(height)
            } else {
                return nil
            }
            if x, ok := m["x"]; ok {
                rectangle.X = toFloat32(x)
            }
            if y, ok := m["y"]; ok {
                rectangle.Y = toFloat32(y)
            }
            return rectangle
        },
        func(m map[string]interface{}) Polygon {
            circle := new(Circle)
            if radius, ok := m["radius"]; ok {
                circle.Radius = toFloat32(radius)
            } else {
                return nil
            }
            if x, ok := m["x"]; ok {
                circle.X = toFloat32(x)
            }
            if y, ok := m["y"]; ok {
                circle.Y = toFloat32(y)
            }
            return circle
        },
    }
    

    And do convert

    var polygons []Polygon
    for _, obj := range newSlice {
        m, ok := obj.(map[string]interface{})
        if !ok {
            panic("invalid struct")
        }
        var p Polygon
        for _, converter := range converters {
            p = converter(m)
            if p != nil {
                break
            }
        }
        if p == nil {
            panic("unknown polygon")
        }
        polygons = append(polygons, p)
    }
    

    https://play.golang.org/p/PrxiMOa_1F

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 AT89C51控制8位八段数码管显示时钟。
  • ¥15 真我手机蓝牙传输进度消息被关闭了,怎么打开?(关键词-消息通知)
  • ¥15 下图接收小电路,谁知道原理
  • ¥15 装 pytorch 的时候出了好多问题,遇到这种情况怎么处理?
  • ¥20 IOS游览器某宝手机网页版自动立即购买JavaScript脚本
  • ¥15 手机接入宽带网线,如何释放宽带全部速度
  • ¥30 关于#r语言#的问题:如何对R语言中mfgarch包中构建的garch-midas模型进行样本内长期波动率预测和样本外长期波动率预测
  • ¥15 ETLCloud 处理json多层级问题
  • ¥15 matlab中使用gurobi时报错
  • ¥15 这个主板怎么能扩出一两个sata口