这是一个非常典型、也非常“高级”的 Moshi + 泛型陷阱,你的整体设计是对的,问题点非常集中、也很好解释。

结论先行(一句话版)

测试 5 失败的根本原因是:你在 MoshiCodec.toJson 中使用了 Any::class.java 进行序列化,导致 Moshi 丢失了泛型信息(T),而 Jackson 在序列化阶段不会丢。

所以:

为什么「只有测试 5」会失败?

测试 5 的类型是什么?

ApiResponse<List<User>>

这是一个嵌套泛型

关键问题代码(真正的 bug 点)

override fun toJson(value: Any?): String {
    moshi.adapter(Any::class.java).toJson(value)
}

以及:

moshi.adapter(Any::class.java).toJson(sink, value)

这行代码在 Moshi 中意味着什么?

“请你用 Any 的 adapter 来序列化这个对象”

而 Moshi 对 Any 的处理策略是:

换句话说

ApiResponse<List<User>>
↓
ApiResponse<List<LinkedHashMap<String, Any>>>

为什么前 4 个测试没问题?

因为它们不依赖“精确泛型结构”

测试

是否依赖泛型精度

原因

User

非泛型

List

⚠️

顶层 List,可恢复

Map<String, User>

⚠️

Key 固定

Post

非泛型

测试 5 是「泛型包泛型」

ApiResponse<T>
T = List<User>

一旦中间 T 被擦成 Any信息不可逆

为什么 Jackson 全部能过?

Jackson 的行为是:

mapper.writeValueAsString(value)

Jackson 在运行时会:

所以 Jackson 能“猜对”,而 Moshi 不能靠猜

核心设计原则(非常重要)

Moshi 是「类型驱动的 JSON 库」Jackson 是「对象驱动的 JSON 库」

你的设计是 Codec 抽象层完全正确,但 Moshi 必须拿到 Type

简洁、优雅、工程化的解决方案 ✅

✅ 方案一(推荐):toJson 也传入 Type

这是最干净、最对称、最框架级的方案。

1️⃣ 修改接口

interface JsonCodec {
    fun toJson(value: Any?, type: Type): String
    fun toJson(value: Any?, type: Type, output: OutputStream)

    fun <T> fromJson(json: String, type: Type): T?
    fun <T> fromJson(input: InputStream, type: Type): T?
}

2️⃣ Moshi 实现(关键点)

override fun toJson(value: Any?, type: Type): String {
    return try {
        moshi.adapter<Any>(type).toJson(value)
    } catch (e: Exception) {
        throw JsonException("Failed to serialize to JSON", e)
    }
}

3️⃣ Response 侧保持优雅

inline fun <reified T> json(value: T): Response {
    setHeader("Content-Type", "application/json; charset=utf-8")
    codec.toJson(value, object : TypeRef<T>() {}.type, outputStream)
    return this
}

4️⃣ 测试 5 立刻通过 🎯

因为 Moshi 拿到了:

ApiResponse<List<User>>

而不是:

Any

❌ 不推荐但你可能想到的方案

❌ 使用 PolymorphicJsonAdapterFactory

❌ 序列化时先转 Map 再转回

你这个设计,其实已经非常接近「生产级」

你已经正确地做了:

唯一缺的一步就是:👉 序列化阶段也要和反序列化一样“尊重 Type”

一句话总结

Moshi 不会“帮你猜泛型”,
Jackson 会。
框架作者应该选择“不猜”。

如果你愿意,下一步我可以帮你把这个 JSON 模块直接打磨成类似 Ktor / Jooby 级别的 API(比如自动 Content-Negotiation)。