一些脚本语言对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},可以自己领悟一下。