定义

JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式

作用

数据标记,存储,传输

特点

  • 读写速度快
  • 解析简单
  • 轻量级
  • 独立于语言,平台
  • 具有自我描叙性

JSON解析

presto的解析数组json 解析jsonarray_presto的解析数组json

语法

JSON建构于两种结构:

  • “名称/值”对的集合(A collection of name/value
    pairs)。不同的语言中,它被理解为对象(object),纪录(record),结构(struct),字典(dictionary),哈希表(hash
    table),有键列表(keyed list),或者关联数组 (associative array)。
  • 值的有序列表(An ordered list of
    values)。在大部分语言中,它被理解为数组(array)。这些都是常见的数据结构。事实上大部分现代计算机语言都以某种形式支持它们。这使得一种数据格式在同样基于这些结构的编程语言之间交换成为可能。

JSON具有以下这些形式:

对象是一个无序的“‘名称/值’对”集合。一个对象以“{”(左括号)开始,“}”(右括号)结束。每个“名称”后 跟一个“:”(冒号);“‘名称/值’ 对”之间使用“,”(逗号)分隔。

presto的解析数组json 解析jsonarray_字符串_02

{
"name": "英语",
"score": 78.3
}

数组是值(value)的有序集合。一个数组以“[”(左中括号)开始,“]”(右中括号)结束。值之间使用“,”(逗号)分隔。

presto的解析数组json 解析jsonarray_presto的解析数组json_03

"courses": [
{
"name": "英语",
"score": 78.3
}
]

值(value)可以是双引号括起来的字符串(string)、数值(number)、 true 、 false 、 null 、对象(object)或者数组(array)。这些结构可以嵌套。

presto的解析数组json 解析jsonarray_java_04

{
"url": "https://qqe2.com",
"name": "欢迎使用JSON在线解析编辑器",
"array": {
"JSON校验": "http://jsonlint.qqe2.com/",
"Cron生成": "http://cron.qqe2.com/",
"JS加密解密": "http://edit.qqe2.com/"
},
"boolean": true,
"null": null,
"number": 123,
"object": {
"a": "b",
"c": "d",
"e": "f"
}
}

字符串(string)是由双引号包围的任意数量Unicode字符的集合,使用反斜线转义。一个字符

(character)即一个单独的字符串(character string)。

字符串(string)与C或者Java的字符串非常相似。

presto的解析数组json 解析jsonarray_java_05

{
"name": "Zero",
}

数值(number)也与C或者Java的数值非常相似。除去未曾使用的八进制与十六进制格式。除去一些编码细节。

presto的解析数组json 解析jsonarray_json_06

{
"age": 28,
}

JSON解析方式

Android Studio自带org.json解析

  • 解析原理:基于文档驱动,需要把全部文件读入到内存中,然后遍历所有数据,根据需要检索想要的数据
  • 具体使用

presto的解析数组json 解析jsonarray_json_07

presto的解析数组json 解析jsonarray_JSON_08

Gson 解析

  • 解析原理:基于事件驱动
  • 解析流程:根据所需取的数据 建立1个对应于JSON数据的JavaBean类,即可通过简单操作解析出所需数据
  • Gson 不要求JavaBean类里面的属性一定全部和JSON数据里的所有key相同,可以按需取数据

具体实现:

  • 创建一个与JSON数据对应的JavaBean类(用作存储需要解析的数据)
    JSON的大括号对应一个对象
  • 对象里面有key,value
  • JavaBean的类属性名 = key
  • JSON的方括号对应一个数组
  • JavaBean里面对应的也是数组
  • 对象里 可以有值/对象
  • 若对象里面只有值,没有key,则说明是纯数组,对应JavaBean里的数组类型
  • 若对象里面有值和key,则说明是对象数组,对应JavaBean里的内部类
  • 对象嵌套
    建立内部类 该内部类对象的名字 = 父对象的key ,类似对象数组
  • presto的解析数组json 解析jsonarray_json_09

  • 转化成JavaBean
  • presto的解析数组json 解析jsonarray_JSON_10


  • presto的解析数组json 解析jsonarray_JSON_11

presto的解析数组json 解析jsonarray_json_12


presto的解析数组json 解析jsonarray_java_13

  • 使用例子

Jackson解析

解析原理:基于事件驱动
解析过程:

  1. 类似 GSON,先创建1个对应于JSON数据的JavaBean类,再通过简单操作即可解析
  2. 与 Gson解析不同的是:GSON可按需解析,即创建的JavaBean类不一定完全涵盖所要解析
    的JSON数据,按需创建属性;但Jackson解析对应的JavaBean必须把Json数据里面的所有key
    都有所对应,即必须把JSON内的数据所有解析出来,无法按需解析

导入Jackson依赖

presto的解析数组json 解析jsonarray_json_14


使用Jackson解析

presto的解析数组json 解析jsonarray_java_15


presto的解析数组json 解析jsonarray_JSON_16

Fastjson解析

导入Fastjson依赖

presto的解析数组json 解析jsonarray_字符串_17


使用Fastjson解析

presto的解析数组json 解析jsonarray_字符串_18

自定义一个JSON解析库

编写一个JSON解析器实际上就是一个方法,它的输入是一个表示JSON的字符串,输出是结构化的对应到语言本身的数据结构
一般来说,解析过程包括词法分析和语法分析两个阶段。词法分析阶段的目标是按照构词规则将 JSON字符串解析成 Token 流,比如有如下的 JSON 字符串

{
"key" : "value",
}

结果词法分析后,得到一组 Token,如下:

{key : value, }

presto的解析数组json 解析jsonarray_presto的解析数组json_19


词法分析解析出 Token 序列后,接下来要进行语法分析。语法分析的目的是根据 JSON 文法检查上面Token 序列所构成的 JSON 结构是否合法。比如 JSON 文法要求非空 JSON 对象以键值对的形式出现,形如 object = {string : value}。如果传入了一个格式错误的字符串,比如

presto的解析数组json 解析jsonarray_presto的解析数组json_20


那么在语法分析阶段,语法分析器分析完 Token name后,认为它是一个符合规则的 Token,并且认为它是一个键。接下来,语法分析器读取下一个 Token,期望这个 Token 是 :。但当它读取了这个Token,发现这个 Token 是 ,,并非其期望的:,于是文法分析器就会报错误。

presto的解析数组json 解析jsonarray_json_21

JSON解析分析小结

  • 通过词法分析是将字符串解析成一组 Token 序列
  • 然后通过语法分析检查输入的 Token 序列所构成的 JSON 格式是否合法

词法分析

按照“构词规则”将 JSON 字符串解析成 Token 流。请注意双引号引起来词–构词规则,所谓构词规则是指词法分析模块在将字符串解析成 Token 时所参考的规则。在 JSON 中,构词规则对应于几种数据类型,当词法解析器读入某个词,且这个词类型符合 JSON 所规定的数据类型时,词法分析器认为这个词符合构词规则,就会生成相应的 Token。这里我们可以参考http://www.json.org/对 JSON 的定义,罗列一下 JSON 所规定的数据类型:

BEGIN_OBJECT({)

END_OBJECT(})

BEGIN_ARRAY([)

END_ARRAY(])

NULL(null)

NUMBER(数字)

STRING(字符串)

BOOLEAN(true/false)

SEP_COLON(:)

SEP_COMMA(,)

当词法分析器读取的词是上面类型中的一种时,即可将其解析成一个 Token。我们可以定义一个枚举类来表示上面的数据类型,如下:

presto的解析数组json 解析jsonarray_JSON_22


在解析过程中,仅有 TokenType 类型还不行。我们除了要将某个词的类型保存起来,还需要保存这个词的字面量。所以,所以这里还需要定义一个 Token 类。用于封装词类型和字面量,如下:

presto的解析数组json 解析jsonarray_java_23


定义好了 Token 类,接下来再来定义一个读取字符串的类

presto的解析数组json 解析jsonarray_presto的解析数组json_24


有了 TokenType、Token 和 CharReader 这三个辅助类,接下来我们就可以实现词法解析器了

presto的解析数组json 解析jsonarray_java_25


presto的解析数组json 解析jsonarray_json_26


presto的解析数组json 解析jsonarray_json_27


presto的解析数组json 解析jsonarray_presto的解析数组json_28


上面的代码是词法分析器的实现,部分代码这里没有贴出来,后面具体分析的时候再贴。先来看看词法分析器的核心方法 start,这个方法代码量不多,并不复杂。其通过一个死循环不停的读取字符,然后再根据字符的类型,执行不同的解析逻辑。上面说过,JSON 的解析过程比较简单。原因在于,在解析时,只需通过每个词第一个字符即可判断出这个词的 Token Type。比如:

  • 第一个字符是{、}、[、]、,、:,直接封装成相应的 Token 返回即可
  • 第一个字符是n,期望这个词是null,Token 类型是NULL
  • 第一个字符是t或f,期望这个词是true或者false,Token 类型是 BOOLEAN
  • 第一个字符是”,期望这个词是字符串,Token 类型为String
  • 第一个字符是0~9或-,期望这个词是数字,类型为NUMBER

正如上面所说,词法分析器只需要根据每个词的第一个字符,即可知道接下来它所期望读取的到的内容是什么样的。如果满足期望了,则返回 Token,否则返回错误。下面就来看看词法解析器在碰到第一个字符是n和”时的处理过程。先看碰到字符n的处理过程:

presto的解析数组json 解析jsonarray_JSON_29


上面的代码很简单,词法分析器在读取字符n后,期望后面的三个字符分别是u,l,l,与 n 组成词 null。如果满足期望,则返回类型为 NULL 的 Token,否则报异常。readNull 方法逻辑很简单,不多说了。接下来看看 string 类型的数据处理过程:

presto的解析数组json 解析jsonarray_JSON_30


presto的解析数组json 解析jsonarray_java_31

string 类型的数据解析起来要稍微复杂一些,主要是需要处理一些特殊类型的字符。JSON 所允许的特殊类型的字符如下:

presto的解析数组json 解析jsonarray_JSON_32


最后一种特殊字符/代码中未做处理,其他字符均做了判断,判断逻辑在 isEscape 方法中。在传入JSON 字符串中,仅允许字符串包含上面所列的转义字符。如果乱传转义字符,解析时会报错。对于STRING 类型的词,解析过程始于字符”,也终于”。所以在解析的过程中,当再次遇到字符”,readString 方法会认为本次的字符串解析过程结束,并返回相应类型的 Token。

上面说了 null 类型和 string 类型的数据解析过程,过程并不复杂,理解起来应该不难。至于 boolean和 number 类型的数据解析过程,大家有兴趣的话可以自己看源码,这里就不在说了。

语法分析

当词法分析结束后,且分析过程中没有抛出错误,那么接下来就可以进行语法分析了。语法分析过程以词法分析阶段解析出的 Token 序列作为输入,输出 JSON Object 或 JSON Array。语法分析器的实现的文法如下:

当词法分析结束后,且分析过程中没有抛出错误,那么接下来就可以进行语法分析了。语法分析过程以词法分析阶段解析出的 Token 序列作为输入,输出 JSON Object 或 JSON Array。语法分析器的实现的文法如下:

presto的解析数组json 解析jsonarray_java_33


presto的解析数组json 解析jsonarray_json_34


语法分析器的实现需要借助两个辅助类,也就是语法分析器的输出类,分别是 JsonObject 和JsonArray。代码如下:

presto的解析数组json 解析jsonarray_JSON_35


presto的解析数组json 解析jsonarray_字符串_36


语法解析器的核心逻辑封装在了 parseJsonObject 和 parseJsonArray 两个方法中,接下来我会详细分析 parseJsonObject 方法,parseJsonArray 方法大家自己分析吧。parseJsonObject 方法实现如下:

presto的解析数组json 解析jsonarray_json_37


presto的解析数组json 解析jsonarray_JSON_38


presto的解析数组json 解析jsonarray_presto的解析数组json_39


presto的解析数组json 解析jsonarray_字符串_40


parseJsonObject 方法解析流程大致如下:

  1. 读取一个 Token,检查这个 Token 是否是其所期望的类型
  2. 如果是,更新期望的 Token 类型。否则,抛出异常,并退出
  3. 重复步骤1和2,直至所有的 Token 都解析完,或出现异常
    上面的步骤并不复杂,但有可能不好理解。这里举个例子说明一下,有如下的 Token 序列:
    {、 id、 :、 1、 }

parseJsonObject 解析完 { Token 后,接下来它将期待 STRING 类型的 Token 或者 END_OBJECT 类型的 Token 出现。于是 parseJsonObject 读取了一个新的 Token,发现这个 Token 的类型是 STRING 类型,满足期望。于是 parseJsonObject 更新期望Token 类型为 SEL_COLON,即:。如此循环下去,直至Token 序列解析结束或者抛出异常退出。
上面的解析流程虽然不是很复杂,但在具体实现的过程中,还是需要注意一些细节问题。比如:

  1. 在 JSON 中,字符串既可以作为键,也可以作为值。作为键时,语法分析器期待下一个 Token 类型为 SEP_COLON。而作为值时,则期待下一个 Token 类型为 SEP_COMMA 或 END_OBJECT。所以这里要判断该字符串是作为键还是作为值,判断方法也比较简单,即判断上一个 Token 的类型即可。如果上一个 Token 是 SEP_COLON,即:,那么此处的字符串只能作为值了。否则,则只能做为键。
  2. 对于整数类型的 Token 进行解析时,简单点处理,可以直接将该整数解析成 Long 类型。但考虑到空间占用问题,对于 [Integer.MIN_VALUE, Integer.MAX_VALUE] 范围内的整数来说,解析成Integer 更为合适,所以解析的过程中也需要注意一下。

Gson原理解析

在这个序列化和反序列化的过程中,

充当的了一个解析器的角色

presto的解析数组json 解析jsonarray_json_41

  1. 反射创建该类型的对象
  2. 把json中对应的值赋给对象对应的属性
  3. 返回该对象

JsonElement

该类是一个抽象类,代表着json串的某一个元素。这个元素可以是一个Json(JsonObject)、可以是一个数组(JsonArray)、可以是一个Java的基本类型(JsonPrimitive)、当然也可以为null(JsonNull);JsonObject,JsonArray,JsonPrimitive,JsonNull都是JsonElement这个抽象类的子类。JsonElement提供了一系列的方法来判断当前的JsonElement

各个JsonElement的关系可以用如下图表示:

presto的解析数组json 解析jsonarray_json_42


JsonObject对象可以看成 name/values的集合,而这写values就是一个个JsonElement,他们的结构可以用如下图表示:

presto的解析数组json 解析jsonarray_字符串_43


JSON-流程简图

presto的解析数组json 解析jsonarray_JSON_44

JsonDeserializer的工作原理

presto的解析数组json 解析jsonarray_presto的解析数组json_45

TypeAdapter的工作原理

presto的解析数组json 解析jsonarray_JSON_46

JSON-适配器模式

presto的解析数组json 解析jsonarray_json_47


presto的解析数组json 解析jsonarray_presto的解析数组json_48


presto的解析数组json 解析jsonarray_字符串_49


presto的解析数组json 解析jsonarray_字符串_50


presto的解析数组json 解析jsonarray_JSON_51

Gson的整体解析原理

presto的解析数组json 解析jsonarray_java_52


presto的解析数组json 解析jsonarray_presto的解析数组json_53

Gson的反射解析机制

presto的解析数组json 解析jsonarray_字符串_54

Gson解析常见的错误

Expected BEGIN_ARRAY but was STRING at line 1 column 27
这种错误一般都是原来是一个字段需要是数组类型,但是事实上给的是””,导致的

解决办法

  1. 让返回null即可解决问题
  2. 用Gson自带的解决方案

presto的解析数组json 解析jsonarray_java_55


presto的解析数组json 解析jsonarray_presto的解析数组json_56


presto的解析数组json 解析jsonarray_json_57