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

 更新时间:2023年02月26日 11:34:20   作者:nil  
这篇文章主要为大家介绍了Golang解析JSON时会遇到的一些坑及解决方法,文中的示例代码讲解详细,对我们学习Go语言有一点的帮助,需要的可以参考一下

写在前面

在写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类型,就报错了

到此这篇关于Golang解析JSON遇到的坑及解决方法的文章就介绍到这了,更多相关Golang解析JSON内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Golang标准库之errors包应用方式

    Golang标准库之errors包应用方式

    Go语言的errors包提供了基础的错误处理能力,允许通过errors.New创建自定义error对象,error在Go中是一个接口,通过实现Error方法来定义错误文本,对错误的比较通常基于对象地址,而非文本内容,因此即使两个错误文本相同
    2024-10-10
  • Go本地测试小技巧解耦任务拆解

    Go本地测试小技巧解耦任务拆解

    这篇文章主要为大家介绍了Go本地测试解耦任务拆解及沟通详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-06-06
  • 一文带你掌握掌握 Golang结构体与方法

    一文带你掌握掌握 Golang结构体与方法

    在 Golang 中,结构体和方法是实现面向对象编程的重要组成部分,也是 Golang 的核心概念之一。在本篇文章中,我们将深入介绍 Golang 结构体与方法的概念、使用方法以及相关的编程技巧和最佳实践
    2023-04-04
  • go语言读取csv文件并输出的方法

    go语言读取csv文件并输出的方法

    这篇文章主要介绍了go语言读取csv文件并输出的方法,实例分析了go语言操作csv文件的技巧,需要的朋友可以参考下
    2015-03-03
  • Go语言标准库flag的具体实现

    Go语言标准库flag的具体实现

    Go语言的flag库提供了一套简单而强大的接口,用于解析命令行参数,本文主要介绍了Go语言标准库flag的具体实现,具有一定的参考价值,感兴趣的可以了解一下
    2024-03-03
  • Go语言中struct的匿名属性特征实例分析

    Go语言中struct的匿名属性特征实例分析

    这篇文章主要介绍了Go语言中struct的匿名属性特征,实例分析了struct的匿名属性特征,对于深入学习Go语言程序设计具有一定参考借鉴价值,需要的朋友可以参考下
    2015-02-02
  • Golang设计模式之组合模式讲解

    Golang设计模式之组合模式讲解

    这篇文章主要介绍了Golang设计模式之组合模式,组合模式针对于特定场景,如文件管理、组织管理等,使用该模式能简化管理,使代码变得非常简洁
    2023-01-01
  • windows下使用vscode搭建golang环境并调试的过程

    windows下使用vscode搭建golang环境并调试的过程

    这篇文章主要介绍了在windows下使用vscode搭建golang环境并进行调试,主要包括安装方法及环境变量配置技巧,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-09-09
  • 详解Go语言各种常见类型的默认值和判空方法

    详解Go语言各种常见类型的默认值和判空方法

    本文主要介绍了详解Go语言各种常见类型的默认值和判空方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-04-04
  • golang 开启opencv图形化编程

    golang 开启opencv图形化编程

    这篇文章主要为大家介绍了golang 开启opencv图形化编程示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-10-10

最新评论