很多Gopher是从PHP转过来的,在重构代码到GO的过程中一定会遇到JSON解析的问题。

PHP是弱类型,所以经常把数字10写成字符串”10″,导致一个表达年龄的JSON变成了这样:

Shell
1
2
3
{
"age": "10"
}
标准库json

golang标准库的json并不能兼容这种情况下的解析,因此如果我们的struct企图使用int来反射这个字段,将会导致decode失败:

Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main
 
import (
"encoding/json"
"fmt"
)
 
type StdStruct struct {
Age int `json:"age"`
}
 
func main() {
s := "{\"age\": \"10\"}"
 
d := &StdStruct{}
 
if err := json.Unmarshal([]byte(s), d); err != nil {
fmt.Println(err)
} else {
fmt.Println(d.Age)
}
}

运行报错:

json: cannot unmarshal string into Go struct field StdStruct.age of type int

第三方json-iterator库

这个库有2个特点:

  • 完全兼容json标准库,也就是API用法完全一样,原有代码不需要改动。
  • 提供了一个兼容模式,可以自动转换字符串/数字弱类型问题,可以转换[]与{}弱类型问题(PHP中的array问题)。

它的基本用法如下:

Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main
 
import (
"fmt"
jsoniter "github.com/json-iterator/go"
)
 
var json = jsoniter.ConfigCompatibleWithStandardLibrary
 
type StdStruct struct {
Age int `json:"age"`
}
 
func main() {
s := "{\"age\": \"10\"}"
 
d := &StdStruct{}
 
if err := json.Unmarshal([]byte(s), d); err != nil {
fmt.Println(err)
} else {
fmt.Println(d.Age)
}
}
  • 删除原先的import encoding/json标准库,引入jsoniter库。
  • 创建一个变量叫做json,取值自jsoniter.ConfigCompatibleWithStandardLibrary。

其他业务代码不需要调整,即可继续按照原先的json.Unmarshal等用法运行。

但是这样的代码仍旧会报错:

main.StdStruct.Age: readUint64: unexpected character: �, error found in #9 byte of …|{“age”: “10”}|…, bigger context …|{“age”: “10”}|…

原因是我们需要手动开启PHP的兼容模式,否则默认只是兼容了json标准库,因此可以运行的代码如下:

Go
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
package main
 
import (
"fmt"
jsoniter "github.com/json-iterator/go"
"github.com/json-iterator/go/extra"
)
 
var json = jsoniter.ConfigCompatibleWithStandardLibrary
 
func init() {
// RegisterFuzzyDecoders decode input from PHP with tolerance.
//  It will handle string/number auto conversation, and treat empty [] as empty struct.
extra.RegisterFuzzyDecoders()
}
 
type StdStruct struct {
Age int `json:"age"`
}
 
func main() {
s := "{\"age\": \"10\"}"
 
d := &StdStruct{}
 
if err := json.Unmarshal([]byte(s), d); err != nil {
fmt.Println(err)
} else {
fmt.Println(d.Age)
}
}

可以得到打印输出:

10

 

后记:我们只需要在main文件里通过init开启1次PHP兼容模式即可,后续引入的模块不需要重复开启。

如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~