Florian Baierl
Florian Baierl

Reputation: 2491

Gson does not correctly serialize LocalDate

I am writing an android application, where I want to serialize instances of this Anime.java class. Its superclass AnimeBase.java has a field called aired, which is of the type DateRange. This DateRange contains two fields:

public LocalDate from;
public LocalDate to;

The serialization is very straight-forward (using gson) like this:

final Gson gson = new Gson();
String data = gson.toJson(obj);

However, in my result, the from and to fields are always empty like here:

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

Here, to was null, so it is missing (and that is okay).

Why is gson not serializing these two LocalDates? Does it have something to do with the DateRanges setter & getter (which are a bit unusual, taking a OffsetDateTime instead of a LocalDate)?

Since these classes stem from a 3rd-party library, is there a good way for me to handle this without duplicating all the model classes in my own application for serializing/deserializing them?

Upvotes: 1

Views: 1785

Answers (2)

da_berni
da_berni

Reputation: 540

I could now find the source of this problem.

Starting in Android 9, Google added something called "Restrictions on non-SDK interfaces" where they restrict the access to not publicly documented SDK interfaces in the Android dalvik runtime.

Since Gson by default uses the ReflectiveTypeAdapterFactory which itself looks for serializable fields in the object to be serialized it heavily depends on Reflection.

Google has documented this behaviour, that the used function Class.getDeclaredFields() which is used by ReflectiveTypeAdapterFactory do return only public accessible Fields, or more concrete, only fields which are whitelisted by Google. https://developer.android.com/guide/app-compatibility/restrictions-non-sdk-interfaces#results-of-keeping-non-sdk

In the referenced documentation, Google explicitly states the java.time.LocalDate fields as greylisted:

Ljava/time/LocalDate;->day:S,greylist-max-o

I am not sure why this access is still working in release mode and the behaviour only is present when the build is debuggable but I suppose this is something which will be removed in future Android versions too.

Because of this we added our own backwards-compatible Serializer (which is similar to the one of @k1r0, but still works with previously serialized values):

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

}

Upvotes: 1

k1r0
k1r0

Reputation: 552

Take a look at https://github.com/gkopff/gson-javatime-serialisers There are serializers for LocalDate objects.

If you choose to create your own serializer:

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

Upvotes: 3

Related Questions