Golang Unmarshal切片类型接口

时间:2017-07-02 13:33:14

标签: json go struct interface unmarshalling

在这个例子中,我将尝试加载包含多边形的2D场景。在代码中,我会有许多不同的结构,如圆形,方形,矩形,五角形等。 所有人都会分享常见的 funcs ,例如Area和Perimeter。 场景本身将存储为多边形界面的切片。

以下是我用来测试此代码的代码:

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)
    }
}

在这个例子中,我设置了一个2个多边形的切片,编组它然后再尝试解组它。 编组的字符串是:

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

但是,当我试图Unmarshal恐慌时:

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

如果这样做,它将非常有用且易于使用。我要说Unmarshall无法区分json字符串中的RectangleCircle,因此无法知道要构建的结构。

有没有办法标记结构或告诉Unmarshal如何区分这些结构?

1 个答案:

答案 0 :(得分:1)

以这种方式来区分json是Circle还是Rectangle。在您的JSON中,没有可以检测对象类型的结构的标识。所以,让我们制定规则。

  • 矩形的基数和高度都大于0
  • 圆的半径大于0

要解组JSON,它应该具有如下所示的常用字段。

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"`
}

这个结构可以存储Rectangle或Circle。然后,添加方法IsCircle和IsRectangle。

func (obj *Object) IsCircle() bool {
    return obj.Radius > 0
}

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

您可以使类似Kind()的方法返回struct的标识。你认为最好。最后,您应该添加ToCircle / ToRectangle方法。

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,
    }
}

如果您想要切割多边形界面,则应将此切片的对象转换为多边形切片,如下所示。

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

更新

另一种方法。制作转换为map [string] interface {}的转换器。转换器可以检测具有查找字段的结构。

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
    },
}

并转换

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