如何在Retrofit中处理Null响应?我在多处遇到此问题
嘿,我太懂你在Retrofit里处理JSON空值/Null值时的抓狂了——你给的示例里,一会儿genre是null,一会儿是数组,story是空字符串,release_date又是null,还有censor_rating用 凑数,这种混合的空值表示确实坑人。我来给你几个实用的解决方案,分场景搞定:
首先,在你的数据模型(不管是Kotlin Data Class还是Java Bean)里,把可能为Null的字段声明为可空类型:
- Kotlin里直接加
?,比如val release_date: String? - Java里用对象类型(比如
String而不是int,因为Primitive类型不能为null)
以Kotlin为例,你的Movie数据类可以先这么定义:
data class Movie( val name: String, // 假设这个字段永远非空 val full_movie: String, val story: String?, // 允许空字符串或null val genre: Any?, // 先设为Any?,因为类型不稳定,后面单独处理 val censor_rating: String?, val release_date: String? )
Gson默认支持解析Null到可空字段,Moshi等序列化库也只需要开启可空支持即可,不用额外复杂配置。
这是最棘手的情况,同一个JSON字段可能返回不同类型。这里有两种靠谱的解决方式:
方法一:自定义Type Adapter(精准处理单个字段)
给genre字段写一个专属的类型转换器,让它自动识别JSON类型并转换:
class GenreTypeAdapter : TypeAdapter<List<String>?>() { override fun write(out: JsonWriter, value: List<String>?) { // 序列化逻辑(如果需要把对象转成JSON的话) value?.let { out.beginArray() it.forEach { genre -> out.value(genre) } out.endArray() } ?: out.nullValue() } override fun read(`in`: JsonReader): List<String>? { return when (`in`.peek()) { JsonToken.NULL -> { `in`.nextNull() null } JsonToken.BEGIN_ARRAY -> { val genres = mutableListOf<String>() `in`.beginArray() while (`in`.hasNext()) { genres.add(`in`.nextString()) } `in`.endArray() genres } else -> { // 遇到其他意外类型(比如字符串),直接跳过并返回空列表 `in`.skipValue() emptyList() } } } }
然后在创建Retrofit的时候,把这个Adapter注册到Gson里:
val gson = GsonBuilder() .registerTypeAdapter( object : TypeToken<List<String>?>() {}.type, GenreTypeAdapter() ) .create() val retrofit = Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(GsonConverterFactory.create(gson)) .build()
现在你的数据类里genre就可以改成val genre: List<String>?,不用再用Any?啦。
方法二:全局JsonDeserializer(处理整个对象的解析)
如果不想单独写TypeAdapter,也可以写一个全局的解析器,手动处理每个字段的特殊情况:
class MovieDeserializer : JsonDeserializer<Movie> { override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Movie { val jsonObj = json.asJsonObject val name = jsonObj.get("name").asString val fullMovie = jsonObj.get("full_movie").asString // 把空字符串也转成null val story = jsonObj.get("story").takeIf { !it.isJsonNull && it.asString.isNotEmpty() }?.asString // 处理genre的类型切换 val genre = when { jsonObj.get("genre").isJsonNull -> null jsonObj.get("genre").isJsonArray -> context.deserialize<List<String>>(jsonObj.get("genre"), object : TypeToken<List<String>>() {}.type) else -> emptyList() } // 把&nbsp;转换成null val censorRating = jsonObj.get("censor_rating").asString.takeIf { it != "&nbsp;" } val releaseDate = jsonObj.get("release_date").takeIf { !it.isJsonNull }?.asString return Movie(name, fullMovie, story, genre, censorRating, releaseDate) } }
注册方式类似:
val gson = GsonBuilder() .registerTypeAdapter(Movie::class.java, MovieDeserializer()) .create()
像示例里的&nbsp;(本质是HTML空格转义),可以在解析的时候直接替换成null或者空字符串,就像上面MovieDeserializer里的处理逻辑:val censorRating = jsonObj.get("censor_rating").asString.takeIf { it != "&nbsp;" }。
如果这类特殊值很多,也可以写一个通用的字符串转换器,统一处理所有字符串字段的替换。
最省心的办法还是和后端同学沟通,统一JSON返回格式:
- 数组类型的字段(比如genre)空的时候返回空数组
[],而不是null - 空值统一用null或者空字符串,不要混用
- 特殊占位符(比如 )直接返回null或空字符串
这样前端解析逻辑会简单很多,避免各种奇怪的兼容问题。
内容的提问来源于stack exchange,提问作者Abhishek Mishra

