Kotlin中将时间戳转为Java 8 ZonedDateTime时遇值为空问题
问题:Kotlin数据类中
createdAt属性始终为空的原因及解决办法 给定的JSON数据
{ "name": "Hungarian Translation", "language": "hu", "created_at": 1676618826043 }
目标Kotlin数据类
data class TranslationBook( val name: String, val language: String, val created_at: Long, val createdAt: ZonedDateTime? = ZonedDateTime.ofInstant(Instant.ofEpochMilli(created_at), ZoneId.of("Asia/Jakarta")), ) : Parcelable
问题原因
核心问题出在JSON解析库的工作逻辑:
- 无论是Gson、Moshi这类常用解析库,在解析JSON到Kotlin数据类时,只会为JSON中存在的字段(
name、language、created_at)赋值。 createdAt没有对应的JSON字段,解析库不会触发它的默认值初始化逻辑——默认值仅在你手动创建类实例(如TranslationBook(...))且不传入createdAt参数时才会生效,解析库通过反射实例化对象时不会执行这段默认值计算代码。
解决办法
方案1:用init块手动赋值
把createdAt的转换逻辑放到类的初始化块中,在created_at被解析赋值后自动计算createdAt:
data class TranslationBook( val name: String, val language: String, val created_at: Long, var createdAt: ZonedDateTime? = null ) : Parcelable { init { createdAt = ZonedDateTime.ofInstant(Instant.ofEpochMilli(created_at), ZoneId.of("Asia/Jakarta")) } }
这里将createdAt改为var是因为需要在init块中赋值,若要保持不可变特性,可以用方案2。
方案2:通过构造函数计算(保持不可变)
隐藏created_at的直接暴露,通过次级构造函数完成转换逻辑:
data class TranslationBook( val name: String, val language: String, val createdAt: ZonedDateTime ) : Parcelable { @JvmOverloads constructor( name: String, language: String, @SerializedName("created_at") created_at: Long ) : this( name, language, ZonedDateTime.ofInstant(Instant.ofEpochMilli(created_at), ZoneId.of("Asia/Jakarta")) ) }
添加@SerializedName("created_at")是为了让解析库识别JSON中的字段,绑定到次级构造函数的参数上。
方案3:自定义JSON解析适配器(推荐)
如果使用Gson或Moshi,编写自定义适配器直接将created_at字段解析为ZonedDateTime类型,让数据类更简洁:
以Gson的TypeAdapter为例:
class TranslationBookAdapter : TypeAdapter<TranslationBook>() { override fun write(out: JsonWriter, value: TranslationBook) { // 序列化逻辑(按需实现) out.beginObject() out.name("name").value(value.name) out.name("language").value(value.language) out.name("created_at").value(value.createdAt?.toInstant()?.toEpochMilli()) out.endObject() } override fun read(`in`: JsonReader): TranslationBook { `in`.beginObject() var name = "" var language = "" var createdAt: ZonedDateTime? = null while (`in`.hasNext()) { when (`in`.nextName()) { "name" -> name = `in`.nextString() "language" -> language = `in`.nextString() "created_at" -> { val timestamp = `in`.nextLong() createdAt = ZonedDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneId.of("Asia/Jakarta")) } else -> `in`.skipValue() } } `in`.endObject() return TranslationBook(name, language, createdAt!!) } }
注册适配器到Gson:
val gson = GsonBuilder() .registerTypeAdapter(TranslationBook::class.java, TranslationBookAdapter()) .create()
内容的提问来源于stack exchange,提问作者pram
相关产品推荐
相关产品推荐

