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);

但是,在我的结果中,fromto 字段始终为空,如下所示:

// ...
"trailer_url": "https://www.youtube.com/embed/SlNpRThS9t8?enablejsapi\u003d1\u0026wmode\u003dopaque\u0026autoplay\u003d1",
"aired": 
  "from": 
,
"episodes": 16,
// ...

这里,to 为空,所以它丢失了(没关系)。

为什么gson不序列化这两个LocalDates?它与DateRanges 的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的主要内容,如果未能解决你的问题,请参考以下文章

将json的日期属性反序列化为LocalDate

Spring Boot LocalDate 字段序列化和反序列化

如何使用 Google 的 Gson API 正确反序列化 JSON?

如何使用Retrofit2,RxJava2,Gson TypeAdapterFActory正确映射Gson?

使用gson,改进将json数组反序列化为模型的正确方法

Gson:有没有更简单的方法来序列化地图