栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Json 解析库 Moshi 的介绍与使用

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Json 解析库 Moshi 的介绍与使用

Moshi

Moshi 是面向 Android、Java 和 Kotlin 的现代 JSON 库,它可以很容易地将 JSON 解析为 Java 和 Kotlin 类。另外,Moshi 是由 Square 公司所开发,且Moshi 的贡献者也是 Gson 的主要贡献者。

传统 Java Json 库(基于反射)用于 Kotlin 主要产生两个问题:

  1. 不支持空安全。在 Kotlin 中变量一般是默认为非空的,若 Json 为空则解析出 null 并不会抛出异常,直到数据被使用时才会抛出,这是很诡异的,因为定义为非空的变量是不需要判空的,但实际上是为 null。
  2. 不支持默认参数。Kotlin 的 data class 和默认参数的语法极大地方便了开发,但是在使用传统 Json 解析库时往往会遇到两个问题:
    • 默认参数失效
    • 解析失败(因为没有无参构造方法)

若使用 Moshi,则不会出现这些问题。

因此,如果项目使用 Kotlin 进行开发,Moshi 无疑是最好的选择。

基础使用

Dependency

implementation("com.squareup.moshi:moshi-kotlin:1.13.0")
kapt("com.squareup.moshi:moshi-kotlin-codegen:1.13.0")

在 Kotlin 中使用 Moshi 有两个方案:

  • 使用 Kotlin 反射
  • 使用注解生成

因为 Kotlin 的反射库将近 2MB,所以一般采用注解生成,而且理论上会比反射效率更高,因为大多数工作在编译器进行编译时就完成了。

Moshi 使用 Adapter 类负责序列化和反序列化,每个 Kotlin 类对应一个 Adapter,命名规则为 数据类名 + JsonAdapter,借助内置的基本数据类型从而实现任意类的解析(即使不是基本数据类型,也可以进行转换),这里可以利用注解来实现。

PS:传统的 Json 库中,每当读取到一个 Json 属性可以利用反射找到 Class
中对应的属性字段进行赋值。现在我们不再使用反射,那么怎么找到是哪个变量呢?那就是在编译时把已知变量全部列出来,没有列出来的变量就忽略,负责这个工作的就是
Adapter。

只需要在需要序列化(反序列化)的类上加上 @JsonClass(generateAdapter = true) 注解就行了。

@JsonClass(generateAdapter)
data class Person(
	val name: String,
	val age: Int,
	val sex: Boolean
)

之后的使用与 Gson 类似。

Bean

val json = "..."

val moshi: Moshi = Moshi.Builder().build()
val jsonAdapter = moshi.adapter(Person::class.java)

val person: Person = jsonAdapter.fromJson(json)

实际使用中可以将 Moshi 作为单例,也可通过 Hilt 提供,此处不展开。

Moshi 异常处理也非常规范,一共只会抛出两种异常:

  1. IOException:读取 Json 过程中出现 IO 异常,或 Json 格式错误。
  2. JsonDataException:数据类型不匹配。

List

如果是要解析成集合,需要先用 Types.newParameterizedType() 包装一下:

val personArrayJson = "..."

val type: Type = Types.newParameterizedType(List::class.java, Person::class.java)
val adapter: JsonAdapter> = moshi.adapter(type)

val persons = adapter.fromJson(personArrayJson)

Map

解析 Map 与 List 类似。

val json = "..."

val type = Types.newParameteriedType(Map::class.java, String::class.java, Int::class.java)
val adapter = moshi.adapter(type)

val map: Map = adapter.fromJson(json)

流式手动解析

Adapter 底层其实使用了 JsonReader 与 JsonWriter 进行(反)序列化,这两个类几乎与 Gson 中一样。(Moshi uses the same streaming and binding mechanisms as Gson. If you’re a Gson user you’ll find Moshi works similarly.)

对于动态(脏)的 Json 数据,我们难以预先得知其包含的字段,此时就可以采用流式解析。Moshi 使用 Okio 作为底层,我们需要通过 Okio 创建数据源来创建 JsonReader。

val json = "..."

val reader = JsonReader.of(Okio.buffer(Okio.source(json)))

fun readPersonArray(reader: JsonReader): List {
	val list = mutableListOf()
	reader.beginArray()
	while(reader.hasNext()) {
		var name = ""
		var age = 0
		var sex = true
		reader.beginObject()
		while(reader.hasNext()) {
			when(reader.nextName()) {
				"name" -> name = reader.nextString()
				"age" -> age = reader.nextInt()
				"sex" -> sex = reader.nextBoolean()
				else -> reader.skipValue()
			}
		}
		reader.endObject()
		val person = Person(name, age, sex)
		list.add(person)
	}
	reader.endArray()
	return list
}

使用 selectName 优化

解析数组时,一些字段名会重复重现,此时 Moshi 不得不进行 UTF-8 解码并分配内存,我们可以事先准备好有可能出现的字段名,直接进行二进制化比对,并返回字段名序列中的下标。

首先使用 JsonReader.Options.of() 创建字段名称数组,然后使用 reader.selectName() 读取并匹配字段名。

val json = "..."

val names = JsonReader.Options.of("name", "age", "sex")
val reader = JsonReader.of(Okio.buffer(Okio.source(json)))

fun readPersonArray(reader: JsonReader): List {
	val list = mutableListOf()
	reader.beginArray()
	while(reader.hasNext()) {
		var name = ""
		var age = 0
		var sex = true
		reader.beginObject()
		while(reader.hasNext()) {
			when(reader.selectName(names)) {
				0 -> name = reader.nextString()
				1 -> age = reader.nextInt()
				2 -> sex = reader.nextBoolean()
				else -> {
					reader.skipName()
					reader.skipValue()
				}
			}
		}
		reader.endObject()
		val person = Person(name, age, sex)
		list.add(person)
	}
	reader.endArray()
	return list
}

自定义 Adapter

更多时候,Json 数据格式是已知的,但其值的格式与 Kotlin Class 定义不同,此时若完全使用流式 API 解析就太麻烦了,此时可以自定义 Adaper,通过 Adapter 控制 Json 与 Class 如何转换。

Adapter 就是一个普通的类,习惯给类名加上 Adapter 后缀以示区分,但实际上它并不继承自任何父类,也无需实现任何接口,只需要定义两个函数分别用于 Json -> Class 与 Class -> Json 的转换,并分别加上 @FromJson 与 @ToJson 注解就行了。

@JsonClass(generateAdapter)
data class Person(
	val name: String,
	val age: Int,
	val sex: Sex // 将性别改为枚举
)

enum class Sex {
    MALE, FEMALE
}

现在 Sex 不再是基础数据类型 Moshi 无法识别,创建一个 SexAdapter 帮助 Moshi 在 Sex 与 Boolean 之间转换:

class SexAdapter {
	@FromJson
	fun fromJson(value: String): Sex {
		return if(value) Sex.MALE else Sex.FEMALE
	}
	
	@ToJson
	fun toJson(sex: Sex): Boolean {
		return sex == Sex.MALE
	}
}

最后进行注册即可成功解析。

val json = "..."
val moshi = Moshi.Builder().add(SexAdapter()).build()
val person = moshi.adapter(Person::class.java).fromJson(json)

事实上,@FromJson 与 @ToJson 所注释的函数的参数类型或返回值类型是任意的,只要它能被 Moshi 识别即可。 换句话说你可以把 Adapter 当成一个中间步骤,许多 Adapter 组成处理链将数据一步步转化成所需的类型。以上面的 Demo 为例,我们接受一个 Boolean 类型的参数并返回 Sex(也就是最终所需的类型),那么整个反序列化过程其实有两个 Adapter 依次参与,分别是 JsonAdapter 和 SexAdapter,前者是 Moshi 内置的,后者是我们自定义并添加到 Moshi 实例的。


参考:https://juejin.cn/post/6844904203010179085——晨鹤

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/827642.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号