英文:
Golang Unmarshal slice of type interface
问题
在这个例子中,我将尝试加载包含多边形的2D场景。在代码中,我将有许多不同的结构体,如Circle、Square、Rectangle、Pentagon等等。所有这些结构体都会共享一些公共的方法,比如Area和Perimeter。场景本身将被存储为接口类型Polygon的切片。
以下是我用来测试的代码:
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个多边形的切片,对其进行了编组(marshal),然后尝试再次进行解组(unmarshal)。编组后的字符串为:
[{"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
如果这个能够工作,那将非常有用且简单易用。我认为Unmarshal
无法从JSON字符串中区分出Rectangle
和Circle
,因此它无法知道应该构建哪个结构体。
有没有办法为结构体添加标签或告诉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
得分: 1
思路是通过区分JSON是圆形还是矩形来进行识别。在你的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"`
}
这个结构体可以存储矩形或圆形。然后,添加IsCircle和IsRectangle方法。
func (obj *Object) IsCircle() bool {
return obj.Radius > 0
}
func (obj *Object) IsRectangle() bool {
return obj.Base > 0 && obj.Height > 0
}
你可以根据自己的想法添加返回结构体标识的Kind()方法。最后,你应该添加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,
}
}
如果你想要Polygon接口的切片,你应该将这个Object的切片转换为Polygon的切片,如下所示。
var polygons []Polygon
for _, obj := range newSlice {
if obj.IsCircle() {
polygons = append(polygons, obj.ToCircle())
} else if obj.IsRectangle() {
polygons = append(polygons, obj.ToRectangle())
}
}
另一种方法是创建一个从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
英文:
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)
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论