In this blog post, we'll look at one more component when it comes to custom deserialization. In the past few blog posts, we've explored how to customize the serialization and deserialization of your data. In both cases we're trying to mitigate differences in the data models between server and client.
In this blog post we'll look at the case where both models are identical, but the client has additional (helper) properties or particular constructors that need to be called when deserializing data.
Of course, this will not be the only post in our Gson series. If you're interested in the other topics, check out our series outline:
Gson Series Overview
- Mapping of Enums
- Mapping of Circular References
- Generics
- Custom Serialization for Simplification (Part 1)
- Changing the Default Serialization with Custom Serialization (Part 2)
- Custom Deserialization Basics
- Custom Instance Creator
- Customizing (De)Serialization via @JsonAdapter
- Custom Deserialization for Calculated Fields
- On-The-Fly-Parsing With Streams
- ProGuard Configuration
Gson Instance Creator
By default, Gson will always create an empty instance of the Java model and then set the properties via reflection. Strictly speaking, the constructors you create in your model are not used at all (by Gson). If your Java models act as simple data holders and provide no further logic or functionality, this behavior has no downsides.
However, if your data model does have additional properties that are set by default or dynamically, you'd have to do some hacking to get around Gson's use of reflection. Luckily, Gson covers that use case as well and lets you implement a clean solution with InstanceCreator
s.
Let's assume the server sends a fairly simple JSON:
{
"age": 26,
"email": "norman@fs.io",
"isDeveloper": true,
"name": "Norman"
}
Your app has an equivalent Java model for it, which Gson can map without any further customization. However, there is one additional property in the client model: Context
. The context could be necessary to store that Java object in a database. It won't come with the JSON and it makes no sense to map it. It's just a helper in our Java application.
Thus, we only have a single constructor in our Java model which requires that Context
object:
public class UserContext {
private String name;
private String email;
private boolean isDeveloper;
private int age;
// additional attribute, which is not part of the data model
private Context context;
public UserContext(Context context) {
this.context = context;
}
}
If you'd use the model above with Gson's default behavior, context
would always be null after deserialization. You need a way to call the constructor and set context
before Gson does its mapping. For exactly that purpose Gson provides InstanceCreator
s.
The way you declare the custom InstanceCreator
s is very similar to the custom serializer, so it might be useful to catch up on that blog post.
String userSimpleJson = ...; // the JSON from the server, see above
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(
UserContext.class,
new UserContextInstanceCreator(getApplicationContext())
);
Gson customGson = gsonBuilder.create();
UserContext customObject = customGson.fromJson(userSimpleJson, UserContext.class);
We're using a custom Gson instance and calling registerTypeAdapter()
to add a new instance creator. The method expects two parameters. First, the type of the data model we want to adjust. Second, an implementation of the instance creator.
In our case, it's the UserContextInstanceCreator
class:
private class UserContextInstanceCreator implements InstanceCreator<UserContext> {
private Context context;
public UserContextInstanceCreator(Context context) {
this.context = context;
}
@Override
public UserContext createInstance(Type type) {
// create new object with our additional property
UserContext userContext = new UserContext(context);
// return it to gson for further usage
return userContext;
}
}
The class has to implement the typed InstanceCreator
interface and override the createInstance(Type type)
method. The constructor of the UserContextInstanceCreator
can be defined by us. In this case, we want to pass the Context
to every model, so we're passing it to the InstanceCreator
and storing it in a field. The instance creator will then call the UserContext(context)
constructor whenever a new instance of the UserContext
model will be created by Gson.
As a result, all following instances of the UserContext
class have the Context
set to the value you've passed to the instance creator. During the regular Java-JSON mapping will Gson still use reflection to set the properties.
Of course, you could do a variety of other things here. You might need to pass a bunch of other additional parameters, besides Context
. Or your constructor does some initialization work, which need to be executed. By declaring an InstanceCreator
you can make sure Gson is actually calling the constructor. If you've other examples, let us know in the comments!
Outlook
In this blog post, you've learned what custom InstanceCreator
s are and what they can be used for. The setup is quite simple and you can get rid of some hacking to make it work with Gson's default behavior. Make sure you know how you can utilize InstanceCreator
s, because you'll need them eventually.
If you've feedback or a question, let us know in the comments or on twitter @futurestud_io.
Make it rock & enjoy coding!