Gson 没有正确序列化 LocalDate

Gson does not correctly serialize LocalDate

我正在编写一个 android 应用程序,我想在其中序列化此 Anime.java class. Its superclass AnimeBase.java has a field called aired, which is of the type 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不序列化这两个LocalDate?它与 DateRanges setter 和 getter 有什么关系吗(这有点不寻常,用 OffsetDateTime 而不是 LocalDate)?

由于这些 类 来自第 3 方库,我有没有一种好的方法来处理这个问题,而无需在我自己的 serializing/deserializing 应用程序中复制所有模型 类他们?

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

我现在可以找到这个问题的根源了。

从 Android 9 开始,Google 添加了一个名为“Restrictions on non-SDK interfaces”的东西,它们限制对 [=33] 中未 public 记录的 SDK 接口的访问=] dalvik 运行时。

由于 Gson 默认使用 ReflectiveTypeAdapterFactory,它本身在要序列化的对象中查找可序列化字段,因此它在很大程度上依赖于反射。

Google 记录了这种行为,ReflectiveTypeAdapterFactory 使用的函数 Class.getDeclaredFields() 仅 return 可访问字段或更多 public具体的,只有被 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)
    }

}