最近使用go在对h2o的REST API进行封装时,发现了h2o的JSON返回值中有Polymorphic
类型的字段。Polymorphic
可以翻译为多态。一个多态的类型可以理解为既可能是float型,也可能是string类型等等。
在我的理解中,一个JSON中每个字段的类型都必须是确定的,在动态语言比如php, js这种,处理这种不确定的类型很方便。但是在go这种静态语言中,一个不确定的类型导致在解析中总是会出现类似于这样子的错误: json: cannot unmarshal string into Go struct field Foo.Value of type int
使用interface{}处理多态的问题
在go中,interface{}是一个空接口,那么所有类型都可以看做实现了这个空接口,因此interface{}可以接收任何类型的值。那么如果一个json既可能是{"mean": 123}
, 又可能是{"mean": "NaN"}
,就可以使用以下结构体:
type Prediction struct {
Mean interface{} `json:"mean"`
}
但是在使用Mean这个变量的时候,就比较麻烦了。
t2 := `{"mean": "NaN"}`
var p2 Prediction
err = json.Unmarshal([]byte(t2), &p2)
if err != nil {
log.Println(err)
} else {
if mean, ok := p2.Mean.(string); ok {
log.Printf("p2 mean: %s \n", mean)
} else if n, ok := p2.Mean.(int); ok {
mean = strconv.Itoa(n)
log.Printf("p2 mean: %s \n", mean)
}
}
实现Unmarshaler和Marshaler接口
type Marshaler interface {
MarshalJSON() ([]byte, error)
}
type Unmarshaler interface {
UnmarshalJSON([]byte) error
}
在使用json.Unmarshal
或json.Marshal
时,会自动调用对应变量类型的UnmarshalJSON
和MarshalJSON
方法。这样就可以定义一个FlexString
类型
type FlexString string
type Prediction struct {
Mean FlexString `json:"mean"`
}
然后实现*FlexString的UnmarshalJSON方法和FlexString的MarshalJSON方法。
func (fs *FlexString) UnmarshalJSON(b []byte) error {
if b[0] == '"' {
return json.Unmarshal(b, (*string)(fs))
}
var n float64
if err := json.Unmarshal(b, &n); err != nil {
return err
}
s := strconv.FormatFloat(n, 'g', 15, 64)
*fs = FlexString(s)
return nil
}
func (fs FlexString) MarshalJSON() ([]byte, error) {
s := string(fs)
n, err := strconv.ParseFloat(s, 64)
if err != nil {
return json.Marshal(s)
}
if math.IsNaN(n) {
return json.Marshal(s)
}
return json.Marshal(n)
}
这样在使用的时候,就可以正常的对多态的Mean
进行json解码以及编码了。
func main() {
t1 := `{"mean": 123.1212}`
var p1 Prediction
err := json.Unmarshal([]byte(t1), &p1)
if err != nil {
log.Println(err)
} else {
log.Println("p1 mean value:", p1.Mean)
}
res1, err := json.Marshal(p1)
if err != nil {
log.Println("res1 error:", err)
} else {
log.Println("res1 :", string(res1))
}
t2 := `{"mean": "NaN"}`
var p2 Prediction
err = json.Unmarshal([]byte(t2), &p2)
if err != nil {
log.Println(err)
} else {
log.Println("p2 mean value:", p2.Mean)
}
res2, err := json.Marshal(p2)
if err != nil {
log.Println("res2 error:", err)
} else {
log.Println("res2 :", string(res2))
}
}
运行结果如下:
2019/07/09 16:38:42 p1 mean value: 123.1212
2019/07/09 16:38:42 res1 : {"mean":123.1212}
2019/07/09 16:38:42 p2 mean value: NaN
2019/07/09 16:38:42 res2 : {"mean":"NaN"}
123.1212在解码再编码之后,仍然是float类型,NaN仍然是string类型。这里有一个注意事项,就是n, err := strconv.ParseFloat(s, 64)
这个方法,如果s是NaN的字符串并不会出错,而是将n变成一个NaN
,因此需要使用math.IsNaN进行检查。