Gson 没有正确序列化 LocalDate
Posted
技术标签:
【中文标题】Gson 没有正确序列化 LocalDate【英文标题】:Gson does not correctly serialize LocalDate 【发布时间】:2021-06-17 08:31:30 【问题描述】:我正在编写一个 android 应用程序,我想在其中序列化这个 Anime.java
类的实例。它的超类AnimeBase.java
有一个名为aired
的字段,其类型为DateRange
。这个DateRange
包含两个字段:
public LocalDate from;
public LocalDate to;
序列化非常简单(使用 gson),如下所示:
final Gson gson = new Gson();
String data = gson.toJson(obj);
但是,在我的结果中,from
和 to
字段始终为空,如下所示:
// ...
"trailer_url": "https://www.youtube.com/embed/SlNpRThS9t8?enablejsapi\u003d1\u0026wmode\u003dopaque\u0026autoplay\u003d1",
"aired":
"from":
,
"episodes": 16,
// ...
这里,to
为空,所以它丢失了(没关系)。
为什么gson不序列化这两个LocalDate
s?它与DateRange
s 的setter 和getter 有什么关系(这有点不寻常,用OffsetDateTime
而不是LocalDate
)?
由于这些类来自第 3 方库,有没有一种好方法可以处理这个问题,而无需在我自己的应用程序中复制所有模型类来序列化/反序列化它们?
【问题讨论】:
这个问题的“有趣”方面,仅当 buildType 声明为debuggable true
时才会发生。当声明为debuggable false
时,一切正常。我只是深入研究了一下,这个问题的根源似乎是 Gson 默认使用 ReflectiveTypeAdapterFactory
并且“反射部分”无法正常工作。 java.time.LocalDate.class.getDeclaredFields()
不返回任何非静态字段,因此序列化值为空。为什么它在不可调试模式下工作我还不清楚。
【参考方案1】:
看看https://github.com/gkopff/gson-javatime-serialisers LocalDate 对象有序列化器。
如果您选择创建自己的序列化程序:
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(new TypeToken<LocalDate>().getType(), new LocalDateConverter());
Gson gson = builder.create();
...
public class LocalDateConverter implements JsonSerializer<LocalDate>, JsonDeserializer<LocalDate>
public JsonElement serialize(LocalDate src, Type typeOfSrc, JsonSerializationContext context)
return new JsonPrimitive(DateTimeFormatter.ISO_LOCAL_DATE.format(src));
public LocalDate deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException
return DateTimeFormatter.ISO_LOCAL_DATE.parse(json.getAsString(), LocalDate::from);
【讨论】:
【参考方案2】:我现在可以找到这个问题的根源。
从 Android 9 开始,Google 添加了一个名为“Restrictions on non-SDK interfaces”的东西,他们在其中限制对 Android dalvik 运行时中未公开记录的 SDK 接口的访问。
由于 Gson 默认使用 ReflectiveTypeAdapterFactory
,它本身在要序列化的对象中查找可序列化字段,因此在很大程度上依赖于反射。
Google 已经记录了这种行为,ReflectiveTypeAdapterFactory
使用的函数 Class.getDeclaredFields()
确实只返回公共可访问的字段,或者更具体地说,只返回被 Google 列入白名单的字段。
https://developer.android.com/guide/app-compatibility/restrictions-non-sdk-interfaces#results-of-keeping-non-sdk
在参考文档中,Google 明确将 java.time.LocalDate 字段列为灰名单:
Ljava/time/LocalDate;->day:S,greylist-max-o
我不确定为什么此访问权限仍在发布模式下工作,并且该行为仅在构建为 debuggable
时存在,但我想这也将在未来的 Android 版本中删除。
因此,我们添加了自己的向后兼容的序列化器(类似于 @k1r0 的序列化器,但仍适用于以前序列化的值):
class LocalDateJsonSerializer : JsonSerializer<LocalDate>, JsonDeserializer<LocalDate>
override fun serialize(src: LocalDate, typeOfSrc: Type, context: JsonSerializationContext): JsonElement
return JsonObject().also
it.addProperty("year", src.year)
it.addProperty("month", src.monthValue)
it.addProperty("day", src.dayOfMonth)
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): LocalDate
val jsonObject = json.asJsonObject
return LocalDate.of(jsonObject["year"].asInt, jsonObject["month"].asInt, jsonObject["day"].asInt)
【讨论】:
以上是关于Gson 没有正确序列化 LocalDate的主要内容,如果未能解决你的问题,请参考以下文章
Spring Boot LocalDate 字段序列化和反序列化
如何使用 Google 的 Gson API 正确反序列化 JSON?