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


猜你喜欢
- 开通QQ邮箱POP3/SMTP服务登录QQ邮箱网址,进入设置选择账户tab页翻到最低下开启此服务获取qq邮箱授权码搭建node接口服务思路创
- TNS简要介绍与应用 Oracle中TNS的完整定义:transparence Network Substrate透明网络底层,监听服务是它
- 我来讲解属性部分, 这是相当有用的, 可要认真上课.首先,jquery中对html标签属性进行操作的关键词是 attr .没错,就4个字母,
- 难道真的要我破解一个么?算了,正好试试我的Python水平。 python版 #coding: gbk import httplib, ur
- 深度学习这个词指的是训练神经网络。深代表着非常大的神经网络。那么神经网络到底是什么呢?看了这篇文章后你就会有很直观的认识了。我们从一个房价预
- 在最古老的JavaScript浏览器里注册事件只能通过内联模式。自从DHTML从根本上改变了你操作页面的方法,事件的注册就必须有扩展性而且要
- 前言:谈到Update 语句大家可能不会陌生,很多情况下我们都会使用它来更新table中的记录。一般而言我们会使用innodb 的存储引擎,
- 首先,Python 完整的异常处理语法结构如下:try: #业务实现代码except Exception1 as e: &nbs
- 本文实例讲述了python处理csv数据的方法。分享给大家供大家参考。具体如下:Python代码:#coding=utf-8__author
- 任何语言都离不开字符,那就会涉及对字符的操作,尤其是脚本语言更是频繁,不管是生产环境还是面试考验都要面对字符串的操作。python的字符串操
- import timefrom selenium import webdriverfrom selenium.webdriver.commo
- 升级背景:为了解决mysql低版本的漏洞,从mysql5.5升级到了8.0.11版本,再次升级到了8.0.17版本(从版本是2019.7.2
- 如何在网上查找链接? 见下:findlinks.html<html><head>
- 在很多网站都有这样的功能,当点击一个全选按钮之后,所有的复选框都会被选中,再点击之后会取消全选,功能非常的人性化,可以省却很多人力,下面就简
- 前言哈喽铁汁们~新年到了,很多小伙伴都会买上一本日历。现在各种主题各种式样的日历有很多,不过你有没有想过自己定制一套专属的个性化电子日历呢?
- 前言QTableWidget是Qt程序中常用的显示数据表格的控件,类似于c#中的DataGrid。QTableWidget是QTableVi
- append()函数描述:在列表ls最后(末尾)添加一个元素object语法:ls.append(object) -> None 无返
- Tornado 文档中提到但是这样只能捕获到handlers中列出的路径请求中的错误。如果只定义了(r"/hello",
- 1 编写 mysql.yaml文件编写yaml如下apiVersion: v1kind: Namespacemetadata:
- 我看blog里,还有很多地方都引用过我写的这个类,转了不少,但自己一直也没发表过,这次正式发表一下。在蓝色理想中有人不懂怎么用,我在baid