Golang解析JSON遇到的坑及解决方法

作者:nil 时间:2024-05-10 13:58:29 

写在前面

在写go的时候经常用到序列化、反序列化,记录一下遇到过的坑。

空指针会被解析成字符串"null"

type Person struct {
Name string
Age  int
}

func main() {
var p *Person
bytes, err := json.Marshal(p)
checkError(err)
fmt.Printf("len:%d, result:%s\n", len(bytes), string(bytes))  // len:4, result:null
}

func checkError(err error) {
if err != nil {
fmt.Printf("err:%+v\n", err)
}
}

json.Marshal一个空指针的时候,得到的结果居然是"null"字符串,我以为是""或者报错。

还有个奇怪的坑

type Person struct {
Name string
Age  int
}

func main() {
var p *Person
s := `null`
err := json.Unmarshal([]byte(s), &p)
checkError(err)
fmt.Printf("p:%+v\n", p) // p:<nil>
}

这个居然不报错,而是得到空指针p

如果把s随便换成其他字符串s := "abc",则报错:invalid character 'a' looking for beginning of value,之前我理解的是null对go来说应该跟abc没有差别,都是字符串。没想到他们是不一样的,下面来深究一下json.UnMarshal底层代码。

在UnMarshal之前它有个checkValid函数

func checkValid(data []byte, scan *scanner) error {
scan.reset()
for _, c := range data {
scan.bytes++
if scan.step(scan, c) == scanError {
return scan.err
}
}
if scan.eof() == scanError {
return scan.err
}
return nil
}

checkValid函数会check每一个字符,调用step函数,step初始值是stateBeginValue

// stateBeginValue is the state at the beginning of the input.
func stateBeginValue(s *scanner, c byte) int {
if isSpace(c) {
return scanSkipSpace
}
switch c {
case '{':
s.step = stateBeginStringOrEmpty
return s.pushParseState(c, parseObjectKey, scanBeginObject)
case '[':
s.step = stateBeginValueOrEmpty
return s.pushParseState(c, parseArrayValue, scanBeginArray)
case '"':
s.step = stateInString
return scanBeginLiteral
case '-':
s.step = stateNeg
return scanBeginLiteral
case '0': // beginning of 0.123
s.step = state0
return scanBeginLiteral
case 't': // beginning of true
s.step = stateT
return scanBeginLiteral
case 'f': // beginning of false
s.step = stateF
return scanBeginLiteral
case 'n': // beginning of null
s.step = stateN
return scanBeginLiteral
}
if '1' <= c && c <= '9' { // beginning of 1234.5
s.step = state1
return scanBeginLiteral
}
return s.error(c, "looking for beginning of value")
}

有这么一段代码,这是处理第一个字符的,发现它对第一个字符是n有特殊处理并且设置下一个字符处理函数为stateN

// stateN is the state after reading `n`.
func stateN(s *scanner, c byte) int {
if c == 'u' {
s.step = stateNu
return scanContinue
}
return s.error(c, "in literal null (expecting 'u')")
}

也就是下一个字符必须是u,再下一个字符处理函数为stateNu

// stateNu is the state after reading `nu`.
func stateNu(s *scanner, c byte) int {
if c == 'l' {
s.step = stateNul
return scanContinue
}
return s.error(c, "in literal null (expecting 'l')")
}

也就是下一个字符必须是l,再下一个字符处理函数为stateNul

// stateNul is the state after reading `nul`.
func stateNul(s *scanner, c byte) int {
if c == 'l' {
s.step = stateEndValue
return scanContinue
}
return s.error(c, "in literal null (expecting 'l')")
}

也就是下一个字符必须是l,再下一个字符处理函数为stateEndValue。

可见checkValid函数对true,false等都有特殊处理。使用时需要注意。

对于json.Marshal函数,通过调试发现它对空指针也有特殊处理

type ptrEncoder struct {
elemEnc encoderFunc
}

func (pe ptrEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
if v.IsNil() {
e.WriteString("null")
return
}
if e.ptrLevel++; e.ptrLevel > startDetectingCyclesAfter {
// We're a large number of nested ptrEncoder.encode calls deep;
// start checking if we've run into a pointer cycle.
ptr := v.Interface()
if _, ok := e.ptrSeen[ptr]; ok {
e.error(&UnsupportedValueError{v, fmt.Sprintf("encountered a cycle via %s", v.Type())})
}
e.ptrSeen[ptr] = struct{}{}
defer delete(e.ptrSeen, ptr)
}
pe.elemEnc(e, v.Elem(), opts)
e.ptrLevel--
}

如果是空指针则返回字符串"null",并且不会报错。

int类型会被解析成float64

type Person struct {
Name string
Age  int
}

func main() {
p := &Person{
Name: "text",
Age:  18,
}

bytes, err := json.Marshal(p)
checkError(err)

pMap := make(map[string]interface{})
err = json.Unmarshal(bytes, &pMap)
checkError(err)
for k, v := range pMap {
fmt.Printf("k:%s,v:%+v, vtype:%v\n", k, v, reflect.TypeOf(v))
}
}

func checkError(err error) {
if err != nil {
fmt.Printf("err:%+v\n", err)
}
}

结果

k:Name,v:text, vtype:string
k:Age,v:18, vtype:float64

显然,Age类型变成了float64。会造成什么问题呢?当int大小超过6位的时候就变成了科学计数法 比如Age=1234567, 结果为

k:Name,v:text, vtype:string
k:Age,v:1.234567e+06, vtype:float64

这个时候如果直接将map更新到db,原本是int类型的字段变成了float类型,就报错了

来源:https://juejin.cn/post/7203880677196398653

标签:Go,解析,JSON
0
投稿

猜你喜欢

  • 详解 Mysql查询结果顺序按 in() 中ID 的顺序排列

    2024-01-16 06:21:51
  • 关于SQL数据库 msdb.dbo.sp_send_dbmail 函数发送邮件的场景分析

    2024-01-14 19:20:11
  • 详解Python字典查找性能

    2022-05-06 10:45:34
  • python源文件的字符编码知识点详解

    2021-04-30 08:05:04
  • python中sleep函数用法实例分析

    2023-10-19 15:33:24
  • Python中pip安装非PyPI官网第三方库的方法

    2021-01-20 15:51:18
  • Java使用正则表达式(regex)匹配中文实例代码

    2023-06-17 07:59:46
  • [译]艺术和设计的差异 (2)

    2009-10-15 12:36:00
  • MAC下Anaconda+Pyspark安装配置详细步骤

    2021-02-11 18:15:51
  • asp 在线备份与恢复sql server数据库的代码

    2010-07-31 18:52:00
  • Pytorch实现的手写数字mnist识别功能完整示例

    2022-10-15 23:38:22
  • 四种Python机器学习超参数搜索方法总结

    2022-03-19 17:29:22
  • 使用python tkinter实现各种个样的撩妹鼠标拖尾效果

    2022-03-10 11:32:31
  • ECMAScript6函数默认参数

    2024-05-13 09:18:24
  • vue-cli项目中怎么使用mock数据

    2024-05-09 15:25:26
  • opencv实现图像平移效果

    2022-09-30 00:22:06
  • Golang如何构造最佳随机密码详解

    2024-05-05 09:29:37
  • php 生成静态页面的办法与实现代码详细版

    2023-10-31 06:05:33
  • asp让网站自动识别手机访问跳转至手机网站

    2014-12-06 09:36:02
  • mysql一对多关联查询分页错误问题的解决方法

    2024-01-28 05:18:44
  • asp之家 网络编程 m.aspxhome.com