我们来看看 Gson 的官方介绍:
Gson是一个Java库,可用于将Java对象转换为其JSON表示形式。它还可以用于将JSON字符串转换为等效的Java对象。Gson能够处理任意Java对象,包括那些你没有源代码的现有对象。
Gson 的主要关注点是 Java。在很多情况下,将其与 Kotlin 或 Scala 等其他 JVM 语言一起使用可能效果很好,但不支持特定于语言的特性,例如 Kotlin 的非null类型或带有默认参数的构造函数。这可能会导致令人困惑的错误行为。
使用 Java 以外的语言时,优先选择对该语言有明确支持的 JSON 库。
如果您的应用程序或库可能在Android上运行,请考虑使用 Kotlin序列化或Moshi的代码生成器,它们使用代码生成而非反射。这样可以避免Gson在应用优化时(通常是由于字段缺失或被混淆)出现运行时崩溃,并能在Android设备上带来更快的性能。对于已经熟悉Gson的用户来说,Moshi的API可能更容易上手。如果您仍然想使用Gson并尝试避免这些崩溃,可以在此处了解具体方法。
以上是 Gson官方介绍,上面直接告知用户在 Android 开发 Kotlin 时代不推荐使用 Gson 而应该使用 Kotlin Serialization。但是我估计相当一大部分 Android 开发者并没有看到过这些描述。为什么?因为 Gson 诞生于 Android Java 语言时代,Kotlin 与 Java 兼容,所以在转移到Kotlin语言开发时,旧时代的优秀工具被继承了下来,并且在多数时候使用,并不会有任何问题,所有 Gson 依旧活跃在 Kotlin 时代的 Andorid 开发中。
那么 Gson 在 Kotlin 上有什么问题呢?我们来看一个例子:
kotlin
data class Student(val name: String = "", val age: String = "")
json
{ "name": "Tom" }
Student类有两个非空字段 val name: String, val age: String, Json 只有一个 name 字段。我们期望如果Json string 没有某个字段则应该使用 Student类字段的默认值,很朴素的期望,对吧?
但实际当你用 Gson 反序列化之后,你会得到一个 name=="Tom" age==null 的 Student 对象,空安全不再安全,同时默认值也一起失效。
那么问题来了,这是为什么呢?
Gson创建因对象类型的不同,分为4种情况:
-
基础类型、以及基础类型的包装类型等由Gson提供的TypeAdapter通过 new 关键字创建
-
枚举类型在EnumTypeAdapter中只是通过枚举名称切换不同的枚举常量,不涉及对象的创建
-
集合和map等容器类型通过Gson内置的对象创建工厂,调用 new 关键字进行创建
-
Java Bean对象的创建比较复杂,分为3种情况,优先级由上到下依次降低:
- 开发者定义了对象创建工厂
InstanceCreator,则使用该工厂创建; - 存在默认的无参构造函数,通过反射构造函数创建;
- 使用
Unsafe API创建。
- 开发者定义了对象创建工厂
java
#ConstructorConstructor.java
public <T> ObjectConstructor<T> get(TypeToken<T> typeToken) {
// 1.获取TypeToken对应的类型(Type)和原始类型(Class<? super T>)
final Type type = typeToken.getType();
Class<? super T> rawType = typeToken.getRawType();
// 2.从instanceCreators中根据类型(Type)获取对应的实例创建器(InstanceCreator)
// 如果存在,则创建并返回一个新的对象构造器,该构造器使用该实例创建器来创建对象
final InstanceCreator<T> typeCreator = (InstanceCreator)this.instanceCreators.get(type);
if (typeCreator != null) {
return new ObjectConstructor<T>() {
public T construct() {
return typeCreator.createInstance(type);
}
};
} else {
// 3.如果步骤2中没有获取到实例创建器,则尝试根据原始类型(rawType)从instanceCreators中获取实例创建器
// 如果存在,则创建并返回一个新的对象构造器,该构造器使用该实例创建器来创建对象。
final InstanceCreator<T> rawTypeCreator = (InstanceCreator)this.instanceCreators.get(rawType);
if (rawTypeCreator != null) {
return new ObjectConstructor<T>() {
public T construct() {
return rawTypeCreator.createInstance(type);
}
};
} else {
// 4.如果步骤2和步骤3都没有获取到实例创建器,则尝试调用newDefaultConstructor方法创建一个默认构造器,如果存在,则返回该构造器(无参构造函数)
ObjectConstructor<T> defaultConstructor = this.newDefaultConstructor(rawType);
if (defaultConstructor != null) {
return defaultConstructor;
} else {
// 5.如果步骤4中没有创建到默认构造器,则尝试调用newDefaultImplementationConstructor方法创建一个默认实现构造器,如果存在,则返回该构造器(容器类型)
ObjectConstructor<T> defaultImplementation = this.newDefaultImplementationConstructor(type, rawType);
// 6.如果步骤5中没有创建到默认实现构造器,则尝试调用newUnsafeAllocator方法创建一个不安全的分配器构造器,最后返回该构造器(Unsafe)
return defaultImplementation != null ? defaultImplementation : this.newUnsafeAllocator(type, rawType);
}
}
}
}
通过上文我们分析可知,当Java类未提供默认的无参构造函数时,Gson会使用 Unsafe API 来创建对象,这种创建对象的方式不会调用构造函数,因此会导致以下几个可能的问题:
- 默认值丢失;
- Kotlin 非空类型失效;
- 初始化块可能不会正常执行
具体源码阅读可以参考:「赏码」更优雅的使用Gson解析
Kotlin Serialization VS Gson
为了解决此问题,官方推出了 kotlin serialization库,让你的 data class能保持它的特性,不会丢失数据。使用也很简单,只需要加一个注解@Serializable
默认值
kotlin
@Serializable data class Student(val name: String, val age: String = "10")
val jsonString = """{ "name": "Tom" }"""
val objByGson =
Gson().fromJson(jsonString, Student::class.java) // 结果 Student(name=Tom, age=null)
val objByKotlinxSerialization =
Json.decodeFromString<Student>(jsonString)// 结果 Student(name=Tom, age=10)
可以看到 kotlinx.serialization 正确的处理了默认值,而Gson忽略了默认值
空安全
kotlin
@Serializable data class Student(val name: String, val age: String)
val jsonString = """{ "name": "Tom" }"""
val objByGson =
Gson().fromJson(jsonString, Student::class.java) // 结果 Student(name=Tom, age=null)
val objByKotlinxSerialization =
Json.decodeFromString<Student>(jsonString)// 结果 抛出异常
为保证 kotlin 的空安全特性,kotlinx.serialization 在反序列化时,如果某个不可空类型的字段在 json 字符串中缺失则会抛出异常:kotlinx.serialization.MissingFieldException: Field 'age' is required for type with serial name 'Student',而Gson 则会序列化成功,但是 data class 的不可空类型定义便被突破,这违背了开发者的期望。这其实是 Gson 在 kotlin 中反序列化遇到最麻烦的问题,在程序运行时,明明定义的类型是不可空,但是为什么会抛出 NullPointerException 呢?
kotlinx.serialization 的这一特性会让我们在声明data class时更加严谨,你需要对字段的可空与否有自己的一番思考 ------ 这显然比无脑将字段声明为可空类型要好的多得多。当然,如果你还是担心由此引发的崩溃问题,同时又不想后续的使用上加上一堆?.的可空调用判断,你可以给其赋上默认值:
kotlin
@Serializable data class Student(val name: String, val age: String)
序列化三方库
不少同学看到这边就想到一点,如果使用注解,那如何学历噩耗三方库的类呢?是个好问题,这确实是 Kotlinx.serialization 不如 Gson 方便的地方。不过官方也提供了相关支持,相对而言麻烦了点。 以下是AI给出的答案:
🌰 示例:序列化 java.time.LocalDateTime
由于你不能给第三方类加 @Serializable,必须:
-
为该类型编写一个
KSerializer<T>实现 -
在使用处显式指定这个 serializer (通过
@Serializable(with = YourSerializer::class))- 或通过
SerializersModule全局注册为 contextual serializer
- 或通过
假设你要序列化 JDK8 的 LocalDateTime(你无法修改它)。
步骤 1:编写 KSerializer
Kotlin
💡 使用
object单例,避免重复创建。
步骤 2:在你的数据类中使用(显式绑定)
Kotlin
object LocalDateTimeSerializer : KSerializer<LocalDateTime> {
override val descriptor = PrimitiveSerialDescriptor("LocalDateTime", kotlinx.serialization.descriptors.PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: LocalDateTime) {
encoder.encodeString(value.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME))
}
override fun deserialize(decoder: Decoder): LocalDateTime {
return LocalDateTime.parse(decoder.decodeString(), DateTimeFormatter.ISO_LOCAL_DATE_TIME)
}
}
✅ 这样就能正常编解码:
Kotlin
val event = Event("Meeting", LocalDateTime.of(2025, 1, 10, 14, 30))
val json = Json.encodeToString(event)
// {"title":"Meeting","timestamp":"2025-01-10T14:30"}
val decoded = Json.decodeFromString<Event>(json)
🔁 替代方案:全局注册为 Contextual Serializer
如果你多处使用 LocalDateTime ,不想每个字段都写 @Serializable(with = ...),可以全局注册:
步骤 1:创建 SerializersModule
Kotlin
val appModule = SerializersModule {
contextual(LocalDateTime::class, LocalDateTimeSerializer)
}
步骤 2:构建带 module 的 Json 实例
Kotlin
val JsonWithTime = Json {
serializersModule = appModule
// 可选:ignoreUnknownKeys = true 等
}
步骤 3:在数据类中不要加 with,直接使用类型
Kotlin
@Serializable
data class Event(
val title: String,
val timestamp: LocalDateTime // ✅ 自动使用 contextual serializer
)
使用时必须用配置好的 Json 实例:
ini
Kotlin
编辑
1val jsonStr = JsonWithTime.encodeToString(event)
2val event2 = JsonWithTime.decodeFromString<Event>(jsonStr)
⚠️ 注意:如果使用默认的
Json.encodeToString()(无 module),会报错:
Serializer for class 'LocalDateTime' is not found.
❗ 重要限制与注意事项
| 问题 | 说明 |
|---|---|
| 不能自动发现 | kotlinx.serialization 不会自动为未注解的类型生成 serializer,必须显式提供 |
| 泛型支持需小心 | 对于 List<Point>,你需要: ListSerializer(PointSerializer) 或在 module 中注册 Point 后使用 serializer<List<Point>>() |
必须用正确的 Json 实例 |
如果用了 contextual,务必使用带 serializersModule 的 Json 对象 |
| 不支持运行时反射 | 所有序列化逻辑必须在编译期确定,无法像 Gson 那样动态解析任意类 |
✅ 最佳实践建议
- 优先使用
@Serializable(with = ...)
适用于局部、明确的场景,代码清晰。 - 多处使用 → 用
SerializersModule+contextual
避免重复注解,但要确保所有编解码都走同一个Json实例。 - 为常用类型封装工具
比如创建CommonSerializers.kt集中管理LocalDateTime、Uri、UUID等。 - 避免直接序列化第三方类
更安全的做法:创建自己的@SerializableDTO,内部转换第三方类(解耦 + 控制字段)。
其他替代库 Moshi
Moshi 也是官方推荐的 kotlin 序列化工具库,本文就不详细介绍了,想了解的同学可以访问Moshi,其来自于大名鼎鼎的 Suqare 开源。
总结
工具就是工具,不一定是最完美的,我们需要根据当前项目需求选择合适开发者自己的工具,不是说一定要用哪个。很显然,如果你的项目Gson 已经存在了很久,想现在替换掉也不现实。但如果你在当前的 Kotlin 项目中使用 Gson,你必须要知道当前存在的风险点,并去规避它。如果你需要在新项目中序列化,那么毫不犹豫,Kotlinx.serialization 或者 Moshi 是你更好的选择!