json 解析结构,很简单想到 Gson、fastJson 等。而这些盛行结构对 kotlin 的支撑并不好,而Moshi 天生对 kotlin 友爱。
相关文章:
Moshi 支撑 enum 空安全
Moshi 支撑 Int/Long 类型 enum
前语
Gson 经过反射反序列化数据,Java 类默许有无参结构函数,关于默许参数能够很好的支撑。关于 kotlin ,咱们常常运用的 data class
,其往往没有无参结构函数,Gson 便会经过 UnSafe 的办法创立实例,成员无法正常初始化默许值。为了牵强能用,只能将结构参数都加上默许值才行,不过这种兼容办法过分隐晦,有潜在的维护危险。
别的,Gson 无法支撑 kotlin 空安全特性。界说为不行空且无默许值的字段,在没有该字段对应的 json 数据时会被赋值为 null,这或许导致运用时引发空指针问题。
Moshi
Moshi 是一个适用于 Android、Java 和 Kotlin 的现代 JSON 库。它能够轻松地将 JSON 解析为 Java 和 Kotlin 类。
val json: String = ...
val moshi: Moshi = Moshi.Builder().build()
val jsonAdapter: JsonAdapter<Person> = moshi.adapter<Person>()
val person = jsonAdapter.fromJson(json)
经过类型适配器 JsonAdapter 能够对数据类型 T 进行序列化/反序列化操作,即 toJson
和 fromJson
办法。
内置类型适配器
moshi 内置支撑以下类型的类适配器:
- 根本类型
- Arrays, Collections, Lists, Sets, Maps
- Strings
- Enums
直接或间接由它们构成的自界说数据类型都能够直接解析。
反射 OR 代码生成
moshi 支撑反射和代码生成两种办法进行 Json 解析。
反射的好处是无需对数据类做任何变化,能够解析 private 和 protected 成员,缺点是引入反射相关库,包体积增大2M多,且反射在性能上稍差。
代码生成的好处是速度更快,缺点是需求对数据类增加注解,无法处理 private 和 protected 成员,用于编译时生成代码,影响编译速度,且注解运用越来越多生成的代码也会越来越多。
反射计划依靠:
implementation("com.squareup.moshi:moshi-kotlin:1.14.0")
代码生成计划依靠(ksp):
plugins {
id("com.google.devtools.ksp").version("1.6.10-1.0.4") // Or latest version of KSP
}
dependencies {
ksp("com.squareup.moshi:moshi-kotlin-codegen:1.14.0")
}
运用代码生成,需求运用注解 @JsonClass(generateAdapter = true)
润饰数据类:
@JsonClass(generateAdapter = true)
data class Person(
val name: String
)
运用反射时,需求增加 KotlinJsonAdapterFactory
到 Moshi.Builder
:
val moshi = Moshi.Builder()
.addLast(KotlinJsonAdapterFactory())
.build()
留意:这儿要运用 addLast 增加 KotlinJsonAdapterFactory
,因为 Adapter 是按增加顺序排列和运用的,假如有自界说的 Adapter,为保证自界说的一直在前,主张经过 addLast
将 KotlinJsonAdapterFactory
一直放在终究。
咱们现在运用的是反射计划,首要考虑到侵入性低,数据类几乎无改动。
其实也能够两种计划都运用,Moshi 会优先运用代码生成的 Adapter,没有的话则走反射。
解析 JSON 数组
关于 json 数据:
[
{
"rank": "4",
"suit": "CLUBS"
},
{
"rank": "A",
"suit": "HEARTS"
}
]
解析:
String cardsJsonResponse = ...;
Type type = Types.newParameterizedType(List.class, Card.class);
JsonAdapter<List<Card>> adapter = moshi.adapter(type);
List<Card> cards = adapter.fromJson(cardsJsonResponse);
和 Gson 类似,为了运行时获取泛型信息,稍微费事点,能够界说扩展函数简化用法:
inline fun <reified T> Moshi.listAdapter(): JsonAdapter<List<T>> {
val type = Types.newParameterizedType(List::class.java, T::class.java)
return adapter(type)
}
简化后:
String cardsJsonResponse = ...
val cards = moshi.listAdapter<Card>().fromJson(cardsJsonResponse)
自界说字段名
假如Json 中字段名和数据类中字段名不一致,或 json 中有空格,能够运用 @Json
注解润饰别名。
{
"username": "jesse",
"lucky number": 32
}
class Player {
val username: String
@Json(name = "lucky number") val luckyNumber: Int
...
}
疏忽字段
运用 @Json(ignore = true)
能够疏忽字段的解析,java 中的 @Transient
注解也能够。
class BlackjackHand(...) {
@Json(ignore = true)
var total: Int = 0
...
}
Java 支撑
Moshi 同样支撑 Java。需求留意的是,和 Gson 一样,Java 类需求有无参结构办法,不然成员变量的默许值无法生效。
public final class BlackjackHand {
private int total = -1;
...
public BlackjackHand(Card hidden_card, List<Card> visible_cards) {
...
}
}
如上,total 的默许值会为 0.
别的,和 Gson 不一样的是,Moshi 并不支撑 JsonElement 这种中心产物,它只支撑内置类型如 List、Map。
自界说 JsonAdapter
假如 json 的数据格式和咱们想要的不一样,就需求咱们自界说 JsonAdapter 来解析了。有意思的是,任何拥有 @Json
和 @ToJson
注解的类都能够成为 Adapter,无需承继 JsonAdapter。
例如 json 格式:
{
"title": "Blackjack tournament",
"begin_date": "20151010",
"begin_time": "17:04"
}
方针数据类界说:
class Event(
val title: String,
val beginDateAndTime: String
)
咱们希望 json 中日期 begin_date 和时间 begin_time 组成 beginDateAndTime 字段。moshi 支撑咱们在 json 和方针数据转化间界说一个中心类,json 和中心类转化后再转化为终究类型。
界说中心类型,本例中即和 json 匹配的数据类型:
class EventJson(
val title: String,
val begin_date: String,
val begin_time: String
)
界说 Adapter :
class EventJsonAdapter {
@FromJson
fun eventFromJson(eventJson: EventJson): Event {
return Event(
title = eventJson.title,
beginDateAndTime = "${eventJson.begin_date} ${eventJson.begin_time}"
)
}
@ToJson
fun eventToJson(event: Event): EventJson {
return EventJson(
title = event.title,
begin_date = event.beginDateAndTime.substring(0, 8),
begin_time = event.beginDateAndTime.substring(9, 14),
)
}
}
将 adapter 注册到 moshi:
val moshi = Moshi.Builder()
.add(EventJsonAdapter())
.build()
这样就能够运用 moshi 直接将 json 转化成 Event
了。本质是将 Json 和方针数据的相互转化加了个中心步骤,先转化为中心产物,再转为终究 Json 或数据实例。
@JsonQualifier:自界说字段类型解析
如下 json,color 为十六进制 rgb 格式的字符串:
{
"width": 1024,
"height": 768,
"color": "#ff0000"
}
数据类,color 为 Int 类型:
class Rectangle(
val width: Int,
val height: Int,
val color: Int
)
Json 中 color 字段类型是 String,数据类同名字段类型为 Int,除了上面介绍的自界说 JsonAdapter 外,还能够自界说同一数据的不同数据类型间的转化。
首要自界说注解:
@Retention(RUNTIME)
@JsonQualifier
annotation class HexColor
运用注解润饰字段:
class Rectangle(
val width: Int,
val height: Int,
@HexColor val color: Int
)
自界说 Adapter:
/** Converts strings like #ff0000 to the corresponding color ints. */
class ColorAdapter {
@ToJson fun toJson(@HexColor rgb: Int): String {
return "#%06x".format(rgb)
}
@FromJson @HexColor fun fromJson(rgb: String): Int {
return rgb.substring(1).toInt(16)
}
}
经过这种办法,同一字段能够有不同的解析办法,或许不多见,但确实有用。
适配器组合
举个比方:
class UserKeynote(
val type: ResourceType,
val resource: KeynoteResource?
)
enum class ResourceType {
Image,
Text
}
sealed class KeynoteResource(open val id: Int)
data class Image(
override val id: Int,
val image: String
) : KeynoteResource(id)
data class Text(
override val id: Int,
val text: String
) : KeynoteResource(id)
UserKeynote
是方针类,其中的 KeynoteResource
或许是 Image
或 Text
,详细是哪个需求依据 type
字段来决定。也就是说 UserKeynote 的解析需求 Image 或 Text 对应的 Adapter 来完结,详细是哪个取决于 type 的值。
明显自带的 Adapter 不能满足需求,需求自界说 Adapter。
先看下 Adapter 中签名要求(参见源码 AdapterMethodsFactory.java):
@FromJson
:
<any access modifier> R fromJson(JsonReader jsonReader) throws <any>
或
<any access modifier> R fromJson(JsonReader jsonReader, JsonAdapter<any> delegate, <any more delegates>) throws <any>
或
<any access modifier> R fromJson(T value) throws <any>
@ToJson
:
<any access modifier> void toJson(JsonWriter writer, T value) throws <any>
或
<any access modifier> void toJson(JsonWriter writer, T value, JsonAdapter<any> delegate, <any more delegates>) throws <any>
或
<any access modifier> R toJson(T value) throws <any>
前面剖析了咱们需求凭借 Image 或 Text 对应的 Adapter,所以运用第二组函数签名:
class UserKeynoteAdapter {
private val namesOption = JsonReader.Options.of("type")
@FromJson
fun fromJson(
reader: JsonReader,
imageJsonAdapter: JsonAdapter<Image>,
textJsonAdapter: JsonAdapter<Text>
): UserKeynote {
// copy 一份 reader,得到 type
val newReader = reader.peekJson()
newReader.beginObject()
var type: String? = null
while (newReader.hasNext()) {
if (newReader.selectName(namesOption) == 0) {
type = newReader.nextString()
}
newReader.skipName()
newReader.skipValue()
}
newReader.endObject()
// 依据 type 做解析
val resource = when (type) {
ResourceType.Image.name -> {
imageJsonAdapter.fromJson(reader)
}
ResourceType.Text.name -> {
textJsonAdapter.fromJson(reader)
}
else -> throw IllegalArgumentException("unknown type $type")
}
return UserKeynote(ResourceType.valueOf(type), resource)
}
@ToJson
fun toJson(
writer: JsonWriter,
userKeynote: UserKeynote,
imageJsonAdapter: JsonAdapter<Image>,
textJsonAdapter: JsonAdapter<Text>
) {
when (userKeynote.resource) {
is Image -> imageJsonAdapter.toJson(writer, userKeynote.resource)
is Text -> textJsonAdapter.toJson(writer, userKeynote.resource)
null -> {}
}
}
}
函数接收一个 JsonReader / JsonWriter 以及若干 JsonAdapter,能够认为该 Adapter 由其他多个 Adapter 组合完结。这种委托的思路在 Moshi 中很常见,比方内置类型 List 的解析,便是委托给了 T 的适配器,并重复调用。
限制
- 不要 Kotlin 类承继 Java 类
- 不要 Java 类承继 Kotlin 类
这是官方着重不要做的,假如你那么做了,发现还没问题,不要侥幸,主张修改,毕竟有有维护危险,且会误导其他维护的人认为这样是可靠合理的。