一些脚本语言对Json格式的要求比较松散,导致他们所提供了接口输出的数据也是一样的,可能存在以下这样子的情况:
- 相同的字段,类型是不固定的
- 将数据包裹在一个数组时,数组元素的类型也是不固定的
而Go语言本身是强类型的,这给解析这样子的数据造成了麻烦,你当然可以使用interface{}
挨个去解析,但随着数据量大了之后,嵌套格式越深,这是一个无底洞。。。
这里将介绍一些go语言json解析的奇技淫巧,让你免遭痛楚。
情景一:相同字段,包含不同的类型
看下方这个Json字符串
1
2
3
4
5
6
7
8
9
10
|
[
{
"plan_a": true,
"plan_b": false
},
{
"plan_a": 1542511080,
"plan_b": 0
}
]
|
这是一个特性的数组,表示用户是否激活PlanA或PlanB计划,这个plan_a字段可能是bool类型,或是一个时间戳。
最终,我在程序上,只需要知道用户是否激活计划A,或激活计划B,我并不关注时间戳的事情,所以相应的代码是这样子定义的:
1
2
3
4
|
type Feature struct {
PlanA bool `json:"plan_a"`
PlanB bool `json:"plan_b"`
}
|
假定你现在就使用这样子的方法去解析Json:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
const featuresText = `
[
{
"plan_a": true,
"plan_b": false
},
{
"plan_a": 1542511080,
"plan_b": 0
}
]`
func main() {
var features []Feature
e := json.Unmarshal([]byte(featuresText), &features)
log.Printf("features: %#v,\nerror: %v", features, e)
}
|
那么输出的结果是:
1
2
|
features: []main.Feature{main.Feature{PlanA:true, PlanB:false}, main.Feature{PlanA:false, PlanB:false}},
error: json: cannot unmarshal number into Go struct field Feature.plan_a of type bool
|
正确的解决方法是实现自定义Json解析的接口:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
type Feature struct {
PlanA bool `json:"plan_a"`
PlanB bool `json:"plan_b"`
}
// 定义一个丑陋的Feature,再使用switch type来解决类型的问题
type uglyFeature struct {
UglyPlanA interface{} `json:"plan_a"` // 时间戳或布尔类型
UglyPlanB interface{} `json:"plan_b"` // 时间戳或布尔类型
}
// 自定义Feature Json解码
func (f *Feature) UnmarshalJSON(b []byte) (e error) {
var ugly uglyFeature
e = json.Unmarshal(b, &ugly)
if e != nil {
return
}
// 是否已开通奖励计划A
switch v := ugly.UglyPlanA.(type) {
case bool:
f.PlanA = v
case float64:
f.PlanA = v > 0
}
// 是否已开通奖励计划B
switch v := ugly.UglyPlanB.(type) {
case bool:
f.PlanA = v
case float64:
f.PlanB = v > 0
}
return nil
}
|
相同的main函数,输出的结果是:
1
2
|
features: []main.Feature{main.Feature{PlanA:false, PlanB:false}, main.Feature{PlanA:true, PlanB:false}},
error: <nil>
|
情景二:数组的类型是不固定的
假定现在的数据更复杂了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
{
"result": [
1,
[
{
"device_sn": "SN0001",
"feature": {
"plan_a": true,
"plan_b": false
}
},
{
"device_sn": "SN0002",
"feature": {
"plan_a": 1542511080,
"plan_b": 0
}
}
]
]
}
|
而你的代码可能是这样子写的:
1
2
3
4
5
6
7
|
func main() {
var result struct {
Result []interface{} `json:"result"`
}
e := json.Unmarshal([]byte(resultText), &result)
log.Printf("result: %#v,\nerror: %v", result, e)
}
|
输出的结果是:
result: struct { Result []interface {} "json:\"result\"" }{Result:[]interface {}{1, []interface {}{map[string]interface {}{"device_sn":"SN0001", "feature":map[string]interface {}{"plan_a":true, "plan_b":false}}, map[string]interface {}{"device_sn":"SN0002", "feature":map[string]interface {}{"plan_a":1.54251108e+09, "plan_b":0}}}}},
error: <nil>
那些特性的数据,不是我们想要的类型Feture
,而是map[string]interface{}
,那么假定我想把它强制转为Feture
,试试看:
1
2
3
4
5
|
slice := result.Result[1].([]interface{})
feature1 := slice[0].(map[string]interface{})["feature"]
log.Printf("feature1: %#v", feature1) // feature1: map[string]interface {}{"plan_a":true, "plan_b":false}
feature1cast := feature1.(Feature)
log.Printf("feature1cast: %#v", feature1cast)
|
输出的结果是
1
2
|
feature1: map[string]interface {}{"plan_a":true, "plan_b":false}
panic: interface conversion: interface {} is map[string]interface {}, not main.Feature
|
呃,根本没办法转换,并且还涉及好多危险的类型转换,任意一个数据出错,都会导致程序panic,程序的健壮性非常差!
正确的做法和上边的差不多,不过更精妙一些:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
type Feature struct {
PlanA bool `json:"plan_a"`
PlanB bool `json:"plan_b"`
}
// 定义一个丑陋的Feature,再使用switch type来解决类型的问题
type uglyFeature struct {
UglyPlanA interface{} `json:"plan_a"` // 时间戳或布尔类型
UglyPlanB interface{} `json:"plan_b"` // 时间戳或布尔类型
}
// 自定义Feature Json解码
func (f *Feature) UnmarshalJSON(b []byte) (e error) {
var ugly uglyFeature
e = json.Unmarshal(b, &ugly)
if e != nil {
return
}
// 是否已开通奖励计划A
switch v := ugly.UglyPlanA.(type) {
case bool:
f.PlanA = v
case float64:
f.PlanA = v > 0
}
// 是否已开通奖励计划B
switch v := ugly.UglyPlanB.(type) {
case bool:
f.PlanA = v
case float64:
f.PlanB = v > 0
}
return nil
}
type Device struct {
DeviceSN string `json:"device_sn"`
Feature Feature `json:"feature"`
}
type Result struct {
Code int `json:"code"`
Devices []Device `json:"devices"`
}
// 自定义Result Json解码
func (p *Result) UnmarshalJSON(b []byte) (e error) {
var code int
var devices []Device
var elements = []interface{}{&code, &devices} // must pass pointers
e = json.Unmarshal(b, &elements)
if e != nil {
return
}
p.Code = code
p.Devices = devices
return
}
|
相应的main函数:
1
2
3
4
5
6
7
|
func main() {
var result struct {
Result Result `json:"result"`
}
e := json.Unmarshal([]byte(resultText), &result)
log.Printf("result: %#v,\nerror: %v", result, e)
}
|
相应的输出:
1
2
|
result: struct { Result main.Result "json:\"result\"" }{Result:main.Result{Code:1, Devices:[]main.Device{main.Device{DeviceSN:"SN0001", Feature:main.Feature{PlanA:false, PlanB:false}}, main.Device{DeviceSN:"SN0002", Feature:main.Feature{PlanA:true, PlanB:false}}}}},
error: <nil>
|
嗯,一切都符合预期,而整个解释的过程,核心关键是在于var elements = []interface{}{&code, &devices}
,可以自己领悟一下。