在这篇文章中,我们将讨论 Square 的名为 Moshi 的 JSON 库。Moshi 帮助我们以更好、更简单的方式对 JSON 进行序列化和反序列化。
因此,在开始之前,让我们将博客分为以下几个部分:
- 为什么我们需要一个库来在android中进行序列化和反序列化?
- 我们如何使用 Moshi?
- Moshi的特点
- 将 Moshi 与列表一起使用。
- 使用 Moshi 进行改造
为什么我们需要一个库来在android中进行序列化和反序列化?
在 Android 中,当我们进行 API 调用时,大多数时候我们都会得到 JSON 作为响应。我们可以调用 JSON 并手动解析它并使用它,或者我们可以使用像 Moshi 这样的库来序列化和反序列化。使用 Moshi 可以帮助我们减少编写的行数并减少出错的可能性。
我们如何使用 Moshi?
在本节中,我们将了解如何使用 Moshi。
假设我们有一个数据类,用户喜欢
data class User(val name: String, val email: String)
并考虑我们有一个名为 user 的变量,定义为,
val user = User("Himanshu", "himanshu@gmail.com")
现在,在 Moshi 的帮助下,我们将其转换为 JSON 结构。为了与 Moshi 合作,我们有 JsonAdapter 类和构建器模式。
因此,我们使用以下方法构建 Moshi,
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
我们还将创建一个 JsonAdapter 类型的变量,这将帮助我们将 JSON 转换为 Object 类,反之亦然。
val jsonAdapter: JsonAdapter<User> = moshi.adapter(User::class.java)
在这里,我们将 User 数据类作为我们想要执行操作的结构传递。现在,要将 User 类的对象转换为我们使用的 JSON,
jsonAdapter.toJson(user)
在这里,我们使用 toJson 从 User 的对象中获取 JSON,如果我们打印它,我们会得到
{"email":"himanshu@gmail.com","name":"Himanshu"}
类似地,我们可以使用 Moshi 将 JSON 映射到一个类。假设我们要将上面的 JSON 转换为 User 类,我们将使用,
jsonAdapter.fromJson("{\"email\":\"himanshu@gmail.com\",\"name\":\"Himanshu\"}")
如果你看到了,我们在这里使用fromJson来帮助我们转换我们作为字符串传递的 JSON。现在,如果我们打印这个,我们会看到,
User(name=Himanshu, email=himanshu@gmail.com)
这就是我们使用 Moshi 将 JSON 转换为对象和将对象转换为 JSON 的方式。
Moshi的特点
Moshi 默认支持几乎所有的数据类型,例如,
- 整数、浮点数等
- 数组和集合
- 字符串
- 枚举
在 Moshi 中,除了上面提到的类型之外,我们还可以创建自己的类型。让我们通过一个例子来理解这一点,
让我们更新 User 类,例如,
data class User(val name:Name)
在这里,我们添加了一个名为 Name 的新数据类,它看起来像,
data class Name(val firstName: String, val lastName: String)
在这里,在 Name 类中,我们采用两个参数,分别称为 firstName 和 lastName。
现在,当从此类中获取 JSON 时,我想要用户的全名,例如firstName + lastName。我们可以在每次解析 JSON 时手动完成,也可以使用 Moshi 添加自己的适配器来为我们完成。
因此,我们将创建一个名为 NameAdapter 的类,
class NameAdapter { }
在这个类中,我们将进行转换。我们将添加两个名为fun fullName()和的函数fun getIndividualNames().
现在的课程看起来像,
class NameAdapter {
@ToJson
fun fullName(name: Name): String {
}
@FromJson
fun getIndividualNames(json: String): Name {
}
}
在这里,您可以看到我们使用ToJson和FromJson注释了 fullName 。getIndividualNames
这意味着,当将此适配器与 Moshi 一起使用时,Moshi 将查找注释。
假设我们想要连接名字和姓氏以返回全名并以 JSON 格式返回,我们将在 fullName 函数中执行此操作,该函数使用ToJson. 现在,fullName 函数看起来像,
@ToJson
fun fullName(name: Name) = name.firstName + " " + name.lastName
同样,由于我们为 JSON 解析添加了 ToJson 转换,我们还需要更新使用 FromJson 注释的 getIndividualNames 函数,该函数将在将 JSON 映射到类时将 JSON 值转换为 Name 数据类中的单个元素。
所以,getIndividaulNames 看起来像,
@FromJson
fun getIndividualNames(fullName: String): Name {
val name = fullName.split(" ")
return Name(name[0], name[1])
}
在这里,我们将字符串 fullName 从第一个空格中分离出来,并在字符串列表中得到两个字符串,分别是名字和姓氏。
最后,为了使用这个适配器,我们将它添加到 Moshi 对象中,同时构建它,
val moshi = Moshi.Builder()
.add(NameAdapter())
.add(KotlinJsonAdapterFactory())
.build()
注意:Moshi 的适配器按优先级排序,因此您总是希望在您自己的自定义适配器之后添加 Kotlin适配器。否则,KotlinJsonAdapterFactory将优先,您的自定义适配器将不会被调用。
JsonAdapter 和上面的一样,
val jsonAdapter: JsonAdapter<User> = moshi.adapter(User::class.java)
现在,当我们在 logcat 中打印 toJson 和 FromJson 时,
jsonAdapter.toJson(user)
和,
jsonAdapter.fromJson("{\"name\":\"Himanshu Singh\"}").toString()
我们得到以下输出,
{"name":"Himanshu Singh"}
User(name=Name(firstName=Himanshu, lastName=Singh))
这就是您可以创建自己的转换适配器的方法。
现在,假设我们只想对类中的一个字段或相同类型的不同字段进行一些条件检查。我们也可以在创建适配器的地方执行此操作,并且只会影响提到的唯一字段。我们将使用注释来做到这一点。
首先,我们将创建一个注释,例如,
@Retention(AnnotationRetention.RUNTIME)
@JsonQualifier
annotation class EmailCheck
在这里,电子邮件检查使用 JsonQualifier 进行注释,可用于特定字段。
现在,我们将更新 User 类,例如,
@JsonClass(generateAdapter = true)
data class User(val name: Name, @EmailCheck val email: String?)
在这里,您可以看到我们使用 EmailCheck 对电子邮件进行了注释,这意味着带有所有 EmailCheck 注释的检查将仅适用于电子邮件字段。Name 类保持不变。
现在,我们将创建一个适配器,它有两个函数,即 toJson 和 fromJson,看起来像,
class EmailAdapter {
fun String.isValidEmail(): Boolean {
return !TextUtils.isEmpty(this) && Patterns.EMAIL_ADDRESS.matcher(this).matches()
}
@ToJson
fun toJson(@EmailCheck email: String?) = email
@FromJson
@EmailCheck
fun fromJson(email: String): String? {
return if (!email.isValidEmail()) {
"No Email Found"
} else email
}
}
在这里,在 EmailAdapter 中,我们将 fromJson 函数注释为EmailCheck,在这里我们将检查我们正在解析以将其映射到类的 JSON 是否不是有效的电子邮件,然后我们将在电子邮件字段中返回No Email Found否则我们返回电子邮件本身。
同样,我们还在 toJson 中使用 EmailCheck 注释了 email 参数,这意味着只允许使用带有 EmailCheck 注释的键。
最后,我们还将使用一个名为KotlinJsonAdapterFactorylike 的新适配器工厂更新 Moshi 构建器,
val moshi = Moshi.Builder()
.add(EmailAdapter())
.add(KotlinJsonAdapterFactory())
.build()
现在,让我们创建一个类似用户对象,
val user = User(Name("Himanshu", "Singh"), email = "00")
并且 jsonAdapter 与我们上面使用它的方式相同。现在,如果我们记录 jsonAdapter.toJson(user),我们会得到以下响应,
{"name":{"firstName":"Himanshu","lastName":"Singh"},"email":"00"}
在这里,您可以看到我们没有传递有效的电子邮件,而只是一个整数值。
最后,当我们使用 fromJson() 解析这个 JSON 时,我们得到输出,
User(name=Name(firstName=Himanshu, lastName=Singh), email=No Email Found)
您可以在我们收到的电子邮件字段中看到,No Email Found因为该电子邮件不是有效的电子邮件。这就是我们如何为各个字段创建适配器并为其添加一些特定条件的方法。
将 Moshi 与列表一起使用
在本节中,我们将学习如何将列表转换为字符串,然后将字符串转换为列表。
考虑一个示例,我们从服务器获取对象列表,我们需要将其存储在应用程序的共享首选项中。我们想将对象列表转换为字符串,因为它只保存原始数据类型来保存它,当我们需要使用它时,我们会将它转换回列表。
现在,假设我们有一个字符串列表,例如,
val names = listOf("Himanshu","Amit", "Ali", "Sumit")
现在我们需要设置类型来映射原始数据,例如,
val type = Types.newParameterizedType(List::class.java, String::class.java)
在这里,我们有 String 数据列表,因此它将String::class作为类来识别元素的类型,而 List 是必须转换的数据类型。
然后要使用这种类型,我们将它与 Moshi 适配器一起使用,例如,
val adapter = moshi.adapter<List<String>>(type)
moshi 的样子,
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
现在,要将数据列表转换为字符串,我们将使用toJson 之类的,
val namesInString: String = adapter.toJson(names)
这会将列表数据映射到字符串,现在如果我们愿意,我们可以存储在 sharedpreference 中。
现在,如果我们想再次反转从字符串到列表的映射,我们将使用我们创建的相同适配器,并使用moshi的 fromJson 将其转换为,
val allNames: List<String>? = adapter.fromJson(namesInString)
在这里,我们需要传递我们需要转换回列表的字符串。
使用 Moshi 进行改造
在本节中,我们将讨论如何在 Android 中使用 Moshi 作为转换器进行 API 调用。我们要访问
https://jsonplaceholder.typicode.com/posts/1
使用 Retrofit 获取单个帖子。所以,首先让我们逐步分解它。
步骤 01
我们将首先设置对build.gradle的依赖,例如,
implementation "com.squareup.retrofit2:retrofit:2.8.1"
implementation "com.squareup.retrofit2:converter-moshi:2.6.2"
步骤 02
我们不会为我们将从 API 获得的 JSON 响应创建一个名为 PostsResponse 的数据类。JSON看起来像,
{
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "tiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}
所以,映射到这个 JSON 的数据类看起来像,
data class PostsResponse(@Json(name = "id")
val id: Int = 0,
@Json(name = "title")
val title: String = "",
@Json(name = "body")
val body: String = "",
@Json(name = "userId")
val userId: Int = 0)
在这里,您可以看到我们对数据类中的所有字段都使用了 @Json 。
@Json采用原始 JSON 响应中的键名,并且仅仅因为我们使用 @Json,我们可以为数据类构造函数变量提供任何名称,并且 JSON 仍将映射到数据类,因为这里的映射发生是因为 @Json注释。
步骤 03
现在,我们将创建接口来保存 API 调用。我们将其称为 APIService 并且看起来像,
interface APIService {
@GET("/posts/1")
fun getSinglePost(): Call<PostsResponse>
}
它将只有一个名为 getSinglePost 的函数,并将返回来自 URL 的唯一帖子。
步骤 04
现在,要设置 Retrofit,我们将首先创建一个 Moshi 实例,例如,
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
然后我们像这样设置改造,
val retrofit = Retrofit.Builder()
.baseUrl("https://jsonplaceholder.typicode.com/")
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
在这里,我们已经传递了 Moshi 的基础 URL 和转换器工厂。我们还将 Moshi 作为我们之前创建的参数传递。
而且,要另外支持KotlinJsonAdapterFactory,还需要以下依赖项,
implementation "com.squareup.moshi:moshi-kotlin:1.9.3"
注: MoshiConverterFactory.create(moshi)
为转换器出厂。
步骤 05
我们现在将进行 API 调用,例如,
retrofit.create(APIService::class.java).getSinglePost().enqueue(object : Callback<PostsResponse> {
override fun onFailure(call: Call<PostsResponse>, t: Throwable) {
Log.d("MainActivity", t.localizedMessage)
}
override fun onResponse(call: Call<PostsResponse>, response: Response<PostsResponse>) {
Log.d("MainActivity", response.body().toString())
}
})
并且在运行应用程序之前,不要忘记在 Manifest 文件中添加 Internet 权限。
现在,如果我们运行应用程序,我们会得到,
PostsResponse(id=1, title=sunt aut facere repellat provident occaecati excepturi optio reprehenderit, body=quia et suscipit
suscipit recusandae consequuntur expedita et cum
reprehenderit molestiae ut ut quas totam
nostrum rerum est autem sunt rem eveniet architecto, userId=1)
因此,API 调用成功。
额外的
假设我们想忽略API 响应中的标题,我们已经更新了模型,例如,
data class PostsResponse(@Json(name = "id")
val id: Int = 0,
@Json(name = "title")
@Transient
val title: String = "",
@Json(name = "body")
val body: String = "",
@Json(name = "userId")
val userId: Int = 0)
在这里,您可以看到我们添加了Transient注释,它将忽略字段标题并将输出打印为,
PostsResponse(id=1, title=, body=quia et suscipit
suscipit recusandae consequuntur expedita et cum
reprehenderit molestiae ut ut quas totam
nostrum rerum est autem sunt rem eveniet architecto, userId=1)
在这里,标题字段为空。
混杂的
- Moshi 的重量很轻。
- 它使用与 GSON 相同的机制。
- 我们可以使用 @Json 注释添加自定义字段名称。