慢聊Go之JSON编码解码中的道法术|Go主题月

简介: 慢聊Go之JSON编码解码中的道法术|Go主题月

前言

昨天,我们讲解了在GO语言中JSON的使用方式。没有学的,可以先学习下,慢聊Go之JSON在Go语言中的道法术|Go主题月

今天,我们来进一步了解下在Go中,encoding/json 程序包的相关潜在知识。

Go的JSON实现

正如我们昨天学到的,Go的JSON实现来源于,程序包encoding/json。

它允许我们无缝地将JSON编码添加到我们的Go对象中。然而,由于广泛使用反射,encoding / json可能是最不被理解的软件包之一。我们今天将深入研究此程序包的工作原理。

什么时候应该使用JSON

通常,当易用性是数据交换的主要目标并且性能低优先级时,则使用JSON。由于JSON是人类可读的,因此如果发生故障,很容易进行调试。另一方面,必须先对二进制协议进行解码,然后才能对其进行分析。

在许多应用中,编码/解码性能的优先级较低,因为它可以轻松地水平缩放。例如,添加额外的服务器来服务API端点通常很简单,因为编码不需要与其他服务器协调。但是,一旦需要添加服务器,数据库可能就不会轻易扩展。

编码流

Go语言中的JSON包提供两种方式把对象的值编码为JSON字符串。

第一种是基于流。编码器,进行编码值到io流 Writer

type Encoder struct {}
func NewEncoder(w io.Writer) *Encoder
func (enc *Encoder) Encode(v interface{}) error

第二种是通过json.Marshal()。使用该方法,会返回编码值的在内存中的字节切片。

func Marshal(v interface{}) ([]byte, error)

当将值传递给这些编码器时,底层JSON库将经历一个复杂的过程,即检查类型定义、编译编码器和递归处理数据中的值。

类型定义

将值传递到编码器时,第一步是查找值的编码器类型。使用Go的reflect包检查类型,json包保存这些类型的内部映射反射类型价值观。对于内置类型(如int、string、map、struct和slice),json包中有硬编码实现。这些相当简单-字符串编码器将字符串值用双引号括起来,并根据需要转义字符,int编码器将整数转换为字符串格式,等等。

注意:在Go中使用reflect库是一个敏感的话题。一方面,它使诸如encoding/json之类的通用运行时编码器成为可能,另一方面,它可能被使用它而不是使用静态类型检查结构的开发人员滥用。

编码器编译

对于不是内置的类型,编码器是动态构建的,进行缓存以供重用。首先,编码器将检查类型是否实现 json.Marshaler:

type Marshaler interface {
  MarshalJSON() ([]byte, error)
}

如果实现了该方法,则封送处理将延迟到类型。如果使用过程中一个类型有一个特殊的JSON表示,并且不应该由JSON包的基于反射的编码器处理,那么这非常有用。

下一步,编码器将检查类型是否实现了encoding.TextMarshaler:

type TextMarshaler interface {
  MarshalText() (text []byte, err error)
}

这些解码器分为两个部分:首先,扫描程序对输入的字节进行令牌化,然后,decodeState将令牌转换为Go对象。

扫描JSON

扫描仪是用于解析JSON内部状态机。它分几个步骤运行。首先,它检查值的第一个字节以确定要分析的令牌的类型。如果它是一个“ {”,则需要解析一个对象;如果它是一个“ [”,则需要解析一个数组。这也适用于简单的值。双引号表示字符串的开头,**“ t”“ f”**表示布尔值的开头,0–9表示数字的开头。

一旦确定要执行的扫描类型,它便会执行特定于类型的功能-例如字符串扫描,数字扫描等。对于复杂对象(例如映射和数组),使用堆栈来跟踪右括号。

前瞻缓冲区

扫描的一个有趣的方面是超前缓冲区。JSON是“ LL(1)可解析的”,这意味着在扫描时它仅需要一个字节缓冲区。该缓冲区用于窥视下一个字节。

例如,数字扫描功能将继续读取字节,直到找到非数字字符为止。但是,由于已经从流中读取了字符,我们需要将其推回缓冲区以供下一个扫描功能使用。这就是超前缓冲区的用途。

解码令牌

一旦令牌被扫描,就需要对其进行解释。这是decodeState的工作。在此阶段,将要解码成的输入值将与每个要处理的令牌匹配。

例如,如果您传入结构类型,则解码器将期望看到**“ {”**令牌。任何其他令牌都将导致解码返回错误。将令牌与值进行匹配的这一阶段涉及大量使用反射包,但是,这些解码器未缓存,因此必须在每次解码时重做反射。

您还可以使用解码器将令牌作为流处理。令牌()和解码器。更多()方法。

自定义封送

就像编码一样,可以在解码期间指定自定义实现。解码器将首先检查类型是否实现json。Unmarshaler

type Unmarshaler interface {
  UnmarshalJSON([]byte) error
}

这使您的类型可以接收该类型的整个JSON值,并对其本身进行解析。如果您要编写自己的优化实现,这将很有用。

接下来,解码器检查值类型是否实现编码。TextUnmarshaler

type TextUnmarshaler interface {
  UnmarshalText(text []byte) error
}

如果您有要使用的类型的字符串表示形式,这将很有用。

延期处理

json的替代品。Unmarshaler是json。RawMessage类型。使用RawMessage,原始JSON表示形式将保存到一个字段,在完成编组后,您可以对其进行处理。如果您需要解释JSON对象中的“类型”字段,然后根据该值更改JSON解析,则可以使用此方法。

type T struct {
  Type  string          `json:"type"`
  Value json.RawMessage `json:"value"`
}
func (t *T) Val() (interface{}, error) {
  switch t.Type {
  case "foo":
    // parse "t.Value" as Foo
  case "bar":
    // parse "t.Value" as Bar
  default:
    return nil, errors.New("invalid type")
  }
}

推迟处理的另一种方法是使用JSON数字。由于JSON不能区分整数和浮点数,因此解码器在解码为interface {}字段时会将数字转换为float64。要推迟解析,您可以使用json。改为Number类型。

type T struct {
  Value json.Number
}
...
if strings.Contains(t.Value, ".") {
  v, err := t.Value.Float64()
  // process as a float
} else {
  v, err := t.Value.Int64()
  // process as an integer
}

我不使用json。Number往往因为我通常解码期间使用静态类型。

格式化打印

JSON通常写为一组长字节,没有多余的空格,但是,这很难读取。您可以通过两种方式设置缩进。如果您有内存中JSON编码的字节片,则可以将其传递给json。缩进()函数:

func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error

前缀参数指定的字符写出来,在每一行和缩进指定用于缩进字符。我通常不使用前缀,但通常使用两个空格或制表符缩进值。

有一个名为json的辅助函数。MarshalIndent()实际上只是调用json。Marshal(),然后是json。缩进()。

如果您使用的是基于流的json。然后,可以使用SetIndent()方法在编码器上设置缩进:

func (enc *Encoder) SetIndent(prefix, indent string)

我发现许多人不了解SetIndent(),他们会编组和缩进一个字节片,然后将结果写入流中。

缩进函数的反函数是Compact()函数:

func Compact(dst *bytes.Buffer, src []byte) error

这会将src重写到目标缓冲区,但删除所有多余的空格。

编码/解码过程中错误处理

json包中有很多错误类型。以下是在编码或解码时可能出问题的列表:

  • 如果传递非指针值进行解码,则实际上是传递值的副本,并且解码器无法将其解码为原始值。解码器捕获到此错误并返回InvalidUnmarshalError
  • 如果您的数据包含无效的JSON,则将返回SyntaxError以及无效字符的字节位置。
  • 如果json返回错误。封送处理程序或编码。TextMarshaler那么它将被包裹在MarshalerError
  • 如果无法将令牌解组为相应的值,则返回UnmarshalTypeError
  • InfinityNaN的浮点值无法在JSON中表示,并且将返回UnsupportedValueError
  • 不能用JSON表示的类型(例如函数,复数,指针等)将返回UnsupportedTypeError
  • 在Go 1.2之前,无效的UTF-8字符将导致返回InvalidUTF8Error。更高版本仅将无效字符转换为U + FFFD,这是“未知字符”的Unicode字符。

尽管这看起来可能是很多错误,但是除了记录错误并请人工干预之外,您无法在代码中处理它们太多了。另外,如果您具有单元测试的涵盖范围,那么许多开发人员可能会在开发时就被抓住。

总结

我们今天聊了下,JSON编码/解码的内部实现。

欢迎点赞!!!


目录
相关文章
|
1月前
|
JSON JavaScript 前端开发
Golang深入浅出之-Go语言JSON处理:编码与解码实战
【4月更文挑战第26天】本文探讨了Go语言中处理JSON的常见问题及解决策略。通过`json.Marshal`和`json.Unmarshal`进行编码和解码,同时指出结构体标签、时间处理、omitempty使用及数组/切片区别等易错点。建议正确使用结构体标签,自定义处理`time.Time`,明智选择omitempty,并理解数组与切片差异。文中提供基础示例及时间类型处理的实战代码,帮助读者掌握JSON操作。
30 1
Golang深入浅出之-Go语言JSON处理:编码与解码实战
|
1月前
|
存储 JSON 数据处理
|
1月前
|
存储 JSON 编解码
python之simplejson:JSON 编/解码器示例详解
python之simplejson:JSON 编/解码器示例详解
25 0
|
1月前
|
JSON 编解码 Go
【Go语言专栏】Go语言中的JSON编码与解码
【4月更文挑战第30天】Go语言内置JSON编码解码支持,简化了数据交换。`json.Marshal`用于将Go结构体转换为JSON,如示例中`Person`结构体的编码。`json.Unmarshal`则将JSON数据反序列化到结构体,需传入结构体变量的地址。错误处理至关重要,特别是在处理大量数据时,要注意性能优化,如避免不必要的转换和重复操作。了解自定义编码解码和最佳实践能提升开发效率。掌握这些技能,有助于构建高效Go应用。
|
7天前
|
存储 JSON JavaScript
【chat-gpt问答记录】python将数据存为json格式和yaml格式
【chat-gpt问答记录】python将数据存为json格式和yaml格式
22 1
|
17天前
|
存储 JSON 分布式计算
DataWorks产品使用合集之如何在数据服务中处理JSON数据
DataWorks作为一站式的数据开发与治理平台,提供了从数据采集、清洗、开发、调度、服务化、质量监控到安全管理的全套解决方案,帮助企业构建高效、规范、安全的大数据处理体系。以下是对DataWorks产品使用合集的概述,涵盖数据处理的各个环节。
36 11
|
12天前
|
存储 JSON JavaScript
使用Python处理JSON格式数据
使用Python处理JSON格式数据
|
20天前
|
JSON JavaScript IDE
JSON 数据格式化方法
JSON 数据格式化方法
30 3
|
22天前
|
XML 存储 JSON
51. 【Android教程】JSON 数据解析
51. 【Android教程】JSON 数据解析
29 2
|
13天前
|
JSON JavaScript 测试技术
掌握JMeter:深入解析如何提取和利用JSON数据
Apache JMeter教程展示了如何提取和使用JSON数据。创建测试计划,包括HTTP请求和JSON Extractor,设置变量前缀和JSON路径表达式来提取数据。通过Debug Sampler和View Results Tree监听器验证提取结果,然后在后续请求和断言中使用这些数据。此方法适用于复杂测试场景,提升性能和自动化测试效率。
26 0