跳到主要内容

JSON 转 Kotlin 数据类:安卓接口解析省去手写模型

把 API 返回的 JSON 直接生成 Kotlin data class,自动推断可空类型和嵌套类,配好 @SerializedName 或 Moshi 注解,安卓开发解析接口不用再一个字段一个字段手敲模型。

发布于 作者 李雷
#Kotlin #JSON #Android #接口解析 #data class

JSON 转 Kotlin 数据类:安卓接口解析省去手写模型

接一个新接口,响应体二三十个字段,全是 snake_case,你大概率经历过这样的事:对着 Postman 里的 JSON,一行一行往 Kotlin 文件里敲 val createdAt: Long,敲到第十五个字段开始怀疑人生,还得防着把 is_active 写成 isactive。手写 model 这件事本身没有技术含量,但极费时间,而且容易在可空性和大整数上犯错。

把 JSON 喂给 /zh/t/json-to-kotlin/,粘进去就能拿到一组干净的 data class,可以直接拖进 .kt 文件。下面说清楚它替你做了哪些容易出错的判断。

data class 到底替你省了什么

Kotlin 的 data class 是为 DTO 量身定的:声明几个属性,编译器白送 equalshashCodetoString、解构,还有 copy()。一个十五字段的响应模型,Java 时代写 POJO 加 getter 加 builder 要两三百行,Kotlin 一个 data class 加几行属性就完事,读起来跟它建模的 JSON 几乎一一对应。

这个工具默认把属性生成成 val(只读),这是 Kotlin DTO 的惯用写法。响应模型几乎从不需要 var,真要改某个字段,生成后把那一个 val 改成 var 就行,一个字符的事。

可空类型不靠猜,靠数组里的缺失信号

可空类型是手写最容易翻车的地方。哪个字段后端有时给有时不给?光看一份样本根本看不出来。

这里的规则很明确:一个 key 在对象数组的某些元素里缺失,或者出现过 JSON null,它就变成 T?。所以你想知道某个字段到底是不是可选的,最好把多次 payload 凑成一个数组粘进去。比如某第三方 webhook 有时带 cancelled_at、有时不带,你把几条记录拼成数组,工具会发现它在部分元素里缺失,直接标成 String?,而不是一个反序列化时会崩的非空字段。

要提醒一句:单个对象没有"key 缺失"信号,除非值本身就是 null,否则每个字段都按非空处理。想从一份样本探索可选性,把它包成 [{...}]

嵌套对象自动抽成命名类,大整数分得清

嵌套对象会被抽成自己的命名类,User.address 出来就是一个独立的 UserAddress,不会全塞进一坨 Map。对象数组还会折叠成一个共享的 data class,[{"a":1},{"a":1,"b":2}] 出来是一个同时带两个字段(b 可空)的类型,而不是两个几乎重复的类。

数字也分得清:能放进 32 位的整数推断成 Int,毫秒级时间戳、雪花 ID 这种大整数推断成 Long,带小数的推断成 Double。这一条很重要,你手写时如果给一个 13 位时间戳写了 Int,运行时直接溢出。

@SerializedName 和 Moshi 注解,按项目选

序列化库注解只在 camelCase 的名字真的偏离原始 key 时才加,name 这种字段保持干净。你按项目已经在用的库选:kotlinx.serialization 给每个类加 @Serializable,只在改名处加 @SerialName;老一点的 Android 项目选 Gson,出 @SerializedName;已经跟 Retrofit 统一用 Moshi 的,选 Moshi 出 @Json(name = ...)

一个真实例子

后端返回这样一段 JSON:

{
  "user_id": 42,
  "display_name": "李雷",
  "created_at": 1718200000000,
  "is_admin": false,
  "profile": { "city": "Beijing", "bio": null }
}

选 Gson,根名设为 User,出来是:

data class User(
    @SerializedName("user_id") val userId: Int,
    @SerializedName("display_name") val displayName: String,
    @SerializedName("created_at") val createdAt: Long,
    @SerializedName("is_admin") val isAdmin: Boolean,
    val profile: UserProfile
)

data class UserProfile(
    val city: String,
    val bio: Any?
)

注意三处判断:createdAt 是 13 位时间戳,推断成 Long 不是 Int;profile 抽成了独立的 UserProfile;bio 只见过 null,推断成 Any?,提示你上线前手动收窄成正确的 String?profile 这个 key 因为本来就是合法标识符,没多加注解。

我自己的用法

我接外部接口时养成一个习惯:不直接对着文档写 model,而是 curl 一次真实响应,先丢进 /zh/t/json-formatter/ 把压扁的一行 JSON 格式化、确认结构没问题,再粘进 json-to-kotlin 生成 data class。这样比照文档手敲少踩两类坑:文档里写"可选"但实际每次都返回的字段不会被我误标成可空,而文档没提的隐藏字段也不会被漏掉。一个原本要十分钟的 model,现在三十秒搞定,省下的时间留给真正要写的解析逻辑。

如果你的后端用的是别的语言栈,同一套思路也适用:Java 项目可以用 json-to-java,Go 项目可以用 json-to-go-struct,前端拿同一份接口可以用 json-to-typescript-interface,模型层从此不用再手抄一遍。


Made by Toolora · Updated 2026-06-13