Reputation: 2491
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 LocalDate
s? Does it have something to do with the DateRange
s 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
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
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