Golang解析JSON遇到的坑及解決方法
寫在前面
在寫go的時候經(jīng)常用到序列化、反序列化,記錄一下遇到過的坑。
空指針會被解析成字符串"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一個空指針的時候,得到的結(jié)果居然是"null"字符串,我以為是""或者報(bào)錯。
還有個奇怪的坑
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> }
這個居然不報(bào)錯,而是得到空指針p
如果把s隨便換成其他字符串s := "abc"
,則報(bào)錯:invalid character 'a' looking for beginning of value
,之前我理解的是null
對go來說應(yīng)該跟abc
沒有差別,都是字符串。沒想到他們是不一樣的,下面來深究一下json.UnMarshal底層代碼。
在UnMarshal之前它有個checkValid
函數(shù)
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
函數(shù)會check每一個字符,調(diào)用step函數(shù),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") }
有這么一段代碼,這是處理第一個字符的,發(fā)現(xiàn)它對第一個字符是n
有特殊處理并且設(shè)置下一個字符處理函數(shù)為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
,再下一個字符處理函數(shù)為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
,再下一個字符處理函數(shù)為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
,再下一個字符處理函數(shù)為stateEndValue。
可見checkValid
函數(shù)對true,false等都有特殊處理。使用時需要注意。
對于json.Marshal函數(shù),通過調(diào)試發(fā)現(xiàn)它對空指針也有特殊處理
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",并且不會報(bào)錯。
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) } }
結(jié)果
k:Name,v:text, vtype:string
k:Age,v:18, vtype:float64
顯然,Age類型變成了float64。會造成什么問題呢?當(dāng)int大小超過6位的時候就變成了科學(xué)計(jì)數(shù)法 比如Age=1234567, 結(jié)果為
k:Name,v:text, vtype:string
k:Age,v:1.234567e+06, vtype:float64
這個時候如果直接將map更新到db,原本是int類型的字段變成了float類型,就報(bào)錯了
到此這篇關(guān)于Golang解析JSON遇到的坑及解決方法的文章就介紹到這了,更多相關(guān)Golang解析JSON內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Go與Rust高性能解析JSON實(shí)現(xiàn)方法示例
- golang解析json數(shù)據(jù)的4種方法總結(jié)
- Go語言學(xué)習(xí)之JSON編碼解析與使用
- Go語言實(shí)現(xiàn)JSON解析的神器詳解
- 一文帶你了解Go語言如何解析JSON
- Go語言JSON解析器gjson使用方法詳解
- Golang實(shí)現(xiàn)解析JSON的三種方法總結(jié)
- golang生成JSON以及解析JSON
- Go?語言?json解析框架與?gjson?詳解
- go語言用八百行代碼實(shí)現(xiàn)一個JSON解析器
- Go語言實(shí)現(xiàn)JSON解析的方法詳解
- GO中Json解析的幾種方式
相關(guān)文章
Golang標(biāo)準(zhǔn)庫之errors包應(yīng)用方式
Go語言的errors包提供了基礎(chǔ)的錯誤處理能力,允許通過errors.New創(chuàng)建自定義error對象,error在Go中是一個接口,通過實(shí)現(xiàn)Error方法來定義錯誤文本,對錯誤的比較通?;趯ο蟮刂?而非文本內(nèi)容,因此即使兩個錯誤文本相同2024-10-10一文帶你掌握掌握 Golang結(jié)構(gòu)體與方法
在 Golang 中,結(jié)構(gòu)體和方法是實(shí)現(xiàn)面向?qū)ο缶幊痰闹匾M成部分,也是 Golang 的核心概念之一。在本篇文章中,我們將深入介紹 Golang 結(jié)構(gòu)體與方法的概念、使用方法以及相關(guān)的編程技巧和最佳實(shí)踐2023-04-04Go語言標(biāo)準(zhǔn)庫flag的具體實(shí)現(xiàn)
Go語言的flag庫提供了一套簡單而強(qiáng)大的接口,用于解析命令行參數(shù),本文主要介紹了Go語言標(biāo)準(zhǔn)庫flag的具體實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2024-03-03windows下使用vscode搭建golang環(huán)境并調(diào)試的過程
這篇文章主要介紹了在windows下使用vscode搭建golang環(huán)境并進(jìn)行調(diào)試,主要包括安裝方法及環(huán)境變量配置技巧,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-09-09