扩展函数与高阶函数在序列化中的应用:Gson/Moshi的扩展函数、Kotlin Serialization的DSL配置、自定义序列化器

序列化这件事,说白了就是把对象变成字符串,再变回来。每个Android开发者都跟它打过交道。但你可能没注意到,Kotlin的扩展函数和高阶函数,能让序列化代码变得特别优雅。

我个人习惯把序列化相关的扩展函数放在一个单独的SerializationExt.kt文件里。这样找起来方便,也方便团队其他人复用。今天我们就聊聊怎么用扩展函数和高阶函数,把Gson、Moshi和Kotlin Serialization用出花来。

Gson的扩展函数封装

先说说Gson。这玩意儿大家太熟了,但每次写Gson().fromJson(json, MyClass::class.java)是不是有点烦?

我在项目中遇到过一个问题:团队里有人忘了传TypeToken,导致泛型信息丢失,运行时直接崩了。后来我写了个扩展函数,把这个问题彻底解决了。

// 基础扩展函数
inline fun <reified T> Gson.fromJson(json: String): T {
    return fromJson(json, object : TypeToken<T>() {}.type)
}

// 带错误处理的版本
inline fun <reified T> Gson.safeFromJson(json: String): Result<T> {
    return try {
        Result.success(fromJson(json, object : TypeToken<T>() {}.type))
    } catch (e: Exception) {
        Result.failure(e)
    }
}

// 使用示例
data class User(val name: String, val age: Int)

val gson = Gson()
val json = """{"name":"张三","age":25}"""

// 以前你得这么写
val user1 = gson.fromJson(json, User::class.java)

// 现在只需要
val user2: User = gson.fromJson(json)

核心思路:利用reified关键字和内联函数,在编译期保留泛型信息。这样就不用手动传TypeToken了。

你想想看,每次少写几个字,项目大了能省不少时间。而且safeFromJson返回Result类型,调用方可以用onSuccessonFailure处理,比try-catch优雅多了。

Moshi的自定义适配器与扩展函数

Moshi比Gson更现代,对Kotlin支持更好。但它也有个痛点:自定义适配器写起来有点啰嗦。

嗯,这里要注意。Moshi的@ToJson@FromJson注解虽然好用,但每个适配器都得单独写一个类。我建议用扩展函数+高阶函数来简化这个过程。

// 用高阶函数创建适配器
inline fun <reified T> Moshi.Builder.addAdapter(
    noinline toJson: (T) -> String,
    noinline fromJson: (String) -> T
): Moshi.Builder {
    val adapter = object : JsonAdapter<T>() {
        override fun fromJson(reader: JsonReader): T? {
            return fromJson(reader.nextString())
        }

        override fun toJson(writer: JsonWriter, value: T?) {
            value?.let { writer.value(toJson(it)) }
        }
    }
    return add(T::class.java, adapter)
}

// 使用示例
data class Money(val amount: BigDecimal, val currency: String)

val moshi = Moshi.Builder()
    .addAdapter<Money>(
        toJson = { "${it.amount}${it.currency}" },
        fromJson = { 
            val parts = it.split(Regex("(?<=\\d)(?=\\D)"))
            Money(BigDecimal(parts[0]), parts[1])
        }
    )
    .build()

小技巧:如果序列化逻辑比较复杂,可以把toJsonfromJson抽成顶层函数,这样测试起来更方便。

我曾经在一个金融项目里用这种方式处理金额序列化。团队里十几个模块,每个模块的金额格式都不一样。用这个扩展函数,每个模块只需要传自己的转换逻辑就行,适配器代码完全复用。

Kotlin Serialization的DSL配置

Kotlin Serialization是官方推荐的方案。它的DSL配置能力特别强,但很多人只用了@Serializable注解,没发挥出全部威力。

说白了,你可以用DSL来配置序列化行为,而不是写一堆注解。

// 自定义序列化模块
val myModule = SerializersModule {
    // 配置多态序列化
    polymorphic(Any::class) {
        subclass(User::class)
        subclass(Admin::class)
    }

    // 配置上下文序列化器
    contextual(LocalDate::class) {
        LocalDateSerializer
    }

    // 配置默认值
    @Serializable
    data class Config(
        val timeout: Long = 5000,
        val retryCount: Int = 3
    )
}

// 使用DSL配置Json实例
val json = Json {
    prettyPrint = true
    ignoreUnknownKeys = true
    coerceInputValues = true
    serializersModule = myModule
    
    // 自定义编码器
    encodeDefaults = true
    explicitNulls = false
}

关键点SerializersModule的DSL配置,让你可以在一个地方集中管理所有序列化规则。比分散的注解好维护得多。

我记得有一次接手一个老项目,里面到处都是@SerialName@Transient注解,改一个字段名要改十几个地方。后来我用SerializersModule统一配置,代码量直接减半。

自定义序列化器实战

有些场景,内置的序列化器搞不定。比如你需要把枚举序列化成中文,或者把时间戳转成特定格式。这时候就得自己写序列化器。

我建议用高阶函数来封装序列化器的创建过程,这样能省不少重复代码。

// 用高阶函数创建序列化器
inline fun <reified T> customSerializer(
    crossinline serialize: (T, JsonEncoder) -> Unit,
    crossinline deserialize: (JsonDecoder) -> T
): KSerializer<T> = object : KSerializer<T> {
    override val descriptor: SerialDescriptor = 
        buildClassSerialDescriptor("Custom${T::class.simpleName}")

    override fun serialize(encoder: JsonEncoder, value: T) {
        serialize(value, encoder)
    }

    override fun deserialize(decoder: JsonDecoder): T {
        return deserialize(decoder)
    }
}

// 使用示例:自定义枚举序列化
enum class Status(val label: String) {
    ACTIVE("启用"),
    INACTIVE("禁用"),
    PENDING("待审核")
}

val StatusSerializer = customSerializer<Status>(
    serialize = { status, encoder ->
        encoder.encodeString(status.label)
    },
    deserialize = { decoder ->
        val label = decoder.decodeString()
        Status.values().first { it.label == label }
    }
)

@Serializable
data class User(
    val name: String,
    @Serializable(with = StatusSerializer::class)
    val status: Status
)

注意:自定义序列化器一定要处理好异常情况。比如上面的deserialize里,如果找不到匹配的枚举值,应该抛出一个有意义的异常,而不是直接崩掉。

你可能会问:为什么不直接用@SerialName?嗯,如果枚举值很多,或者标签需要从配置文件读取,用自定义序列化器更灵活。我在一个多语言项目里就用这种方式,枚举的标签跟着语言配置走,前端完全不用改代码。

知识体系总览

下面这张图总结了扩展函数和高阶函数在序列化中的核心应用场景:

扩展函数与高阶函数在序列化中的应用 Gson 扩展函数 reified + fromJson 泛型保留 safeFromJson 返回 Result Moshi 高阶适配器 addAdapter 高阶函数封装 toJson / fromJson 解耦 Kotlin Serialization DSL SerializersModule 配置 Json DSL 实例化 核心目标:减少样板代码 · 统一序列化逻辑 · 提升可测试性 自定义序列化器:高阶函数 + KSerializer

从这张图能看出来,三种方案的核心思路是一样的:用扩展函数减少重复代码,用高阶函数抽象变化的部分。Gson侧重泛型保留,Moshi侧重适配器复用,Kotlin Serialization侧重DSL配置。但底层逻辑都是函数式编程的那一套——把行为参数化。

好了,这一章的内容就到这里。序列化看起来是个小问题,但处理不好会引发线上bug。用扩展函数和高阶函数把这些逻辑封装好,能省心不少。

公众号:蓝海资料掘金营,微信deep3321