I've inherited some code that saves our application state as JSON using Gson, and then reads it using fromJson.
Gson gson = createGson();
gson.fromJson(objString, myClass);
One of the fields being saved is a Location. Unfortunately, very occasionally the parsing of that saved data fails because my saved Location includes an mClassLoader in its mExtras, and the Gson library fails to create the ClassLoader with this error:
RuntimeException: Failed to invoke protected java.lang.ClassLoader() with no args
Does anybody know why a ClassLoader is being included in the extras for my Location, and whether it should be ending up in the JSON representation?
I'm assuming I can fix this by just saving the key fields from the Location object individually (e.g. longitude, latitude, altitude, time, accuracy), but it would be nice to save out the Location object if possible.
I saw there is an ExclusionStrategy object I could use to exclude fields, but I wasn't sure if I could/should use that to exclude the the extras from inside my Location...
FYI, here's the JSON data for my Location object (with the longitude and latitude changed to hide me):
{
<snip>
"lastKnownLocation": {
"mResults": [
0,
0
],
"mProvider": "gps",
"mExtras": {
"mParcelledData": {
"mOwnObject": 1,
"mObject": 5525040
},
"mClassLoader": {
"packages": {}
},
"mMap": {},
"mHasFds": false,
"mFdsKnown": true,
"mAllowFds": true
},
"mDistance": 0,
"mTime": 1354658984849,
"mAltitude": 5.199999809265137,
"mLongitude": -122.4376,
"mLon2": 0,
"mLon1": 0,
"mLatitude": 37.7577,
"mLat1": 0,
"mLat2": 0,
"mInitialBearing": 0,
"mHasSpeed": true,
"mHasBearing": false,
"mHasAltitude": true,
"mHasAccuracy": true,
"mAccuracy": 16,
"mSpeed": 0,
"mBearing": 0
},
<snip>
}
Here's an example what the mExtras contains when the code doesn't crash:
"mExtras": {
"mParcelledData": {
"mOwnsNativeParcelObject": true,
"mNativePtr": 1544474480
},
"mHasFds": false,
"mFdsKnown": true,
"mAllowFds": true
}
The problem is you're attempting to just straight convert a system-provided class (Location
) to JSON. And, as you see you run into problems with serializing internal state / Java specific things. JSON is meant as a semi-generic way to pass information around.
You can't use the @Expose
annotation easily because it's not your class; that would require modifying the source code for Location
or via a fairly extensive process of adding them at runtime using jassist (see: http://ayoubelabbassi.blogspot.com/2011/01/how-to-add-annotations-at-runtime-to.html)
Looking at the Location
class, I'd simply create a custom Gson Serializer and Deserializer and be done with it. What you're actually interested in is the GPS data, not internals of the class itself. You just construct the JSON containing the information you need in the serializer using the getters, then in the Deserializer you create a new instance of Location
and populate it with the information from the supplied JSON using the public setters.
class LocationSerializer implements JsonSerializer<Location>
{
public JsonElement serialize(Location t, Type type,
JsonSerializationContext jsc)
{
JsonObject jo = new JsonObject();
jo.addProperty("mProvider", t.getProvider());
jo.addProperty("mAccuracy", t.getAccuracy());
// etc for all the publicly available getters
// for the information you're interested in
// ...
return jo;
}
}
class LocationDeserializer implements JsonDeserializer<Location>
{
public Location deserialize(JsonElement je, Type type,
JsonDeserializationContext jdc)
throws JsonParseException
{
JsonObject jo = je.getAsJsonObject();
Location l = new Location(jo.getAsJsonPrimitive("mProvider").getAsString());
l.setAccuracy(jo.getAsJsonPrimitive("mAccuracy").getAsFloat());
// etc, getting and setting all the data
return l;
}
}
Now in your code you use GsonBuilder
and register the classes:
...
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Location.class, new LocationDeserializer());
gsonBuilder.registerTypeAdapter(Location.class, new LocationSerializer());
Gson gson = gsonBuilder.create();
...
That should pretty much take care of it.
Consider to create a custom Object that models your GSON, parsing the content Object per Object and field per field
something like
JSONObject lastKnownLocation = obj.getJSONObject("lastKnownLocation");
JSONArray mResults = lastKnownLocation.getJSONArray("mResults");
etc...
MyGSON mg=new MyGSON(lastKnownLocation, mResults etc....);
So you can obtain the full control of the parsing, and adding a try\catch block in the critical mExtra part you can exclude the block, or manage the exception as you want, easily.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With