Leveraging the Gson Library - kmolo/android_guides GitHub Wiki
Google's Gson library provides a powerful framework for converting between JSON strings and Java objects. This library helps to avoid needing to write boilerplate code to parse JSON responses yourself. It can be used with any networking library, including the Android Async HTTP Client and Retrofit.
Add the following line to your Gradle configuration:
dependencies {
compile 'com.google.code.gson:gson:2.3'
}
You can auto-generate many of your code as described in this guide, but for now, we'll walk through how you can do so manually. First, we need to know what type of JSON response we will be receiving. Let's use the Rotten Tomatoes API as an example and show how to create Java objects that will be able to parse the latest box office movies. Based on the JSON response returned for this API call, let's first define how a basic movie representation should look like:
class Movie {
String id;
String title;
int year;
public String getId() {
return id;
}
public String getTitle() {
return title;
}
public int getYear() {
return year;
}
};
By default, the Gson library will map the fields defined in the class to the JSON keys defined in the response. For instance, the fields id
, title
, and year
will be mapped automatically. We do not need any special annotations unless the field names and JSON keys are different.
In this specific case, the Movie class will correspond to each individual movie element:
movies: [
{
id: "771305050",
title: "Straight Outta Compton",
year: 2015,
},
{
id: "771357161",
title: "Mission: Impossible Rogue Nation",
year: 2015
}
]
Because the API returns a list of movies and not just an individual one, we also need to create a class that will map the movies
key to a list of Movie objects.
public class BoxOfficeMovieResponse {
List<Movie> movies;
// public constructor is necessary for collections
public Movies() {
movies = new ArrayList<Movie>();
}
Note below that a public constructor is needed to initialize the list. Without this constructor, the Gson library will be unable to parse the response correctly for collection types.
Assuming we have the JSON response in string form, we simply need to use the Gson library and using the fromJson
method. The first parameter for this method is the JSON response in text form, and the second parameter is the class that should be mapped. We can create a static method that returns back a BoxOfficeMovieResponse
class as shown below:
public class BoxOfficeMovieResponse {
.
.
.
public static BoxOfficeMovieResponse parseJSON(String response) {
Gson gson = new GsonBuilder().create();
BoxOfficeMovieResponse boxOfficeMovieResponse = gson.fromJson(response, Response.class);
return boxOfficeMovieResponse;
}
}
The Gson Builder class enables a variety of different options that help provide more flexibility for the JSON parsing. Before we instantiate a Gson parser, it's important to know what options are available using the Builder class.
GsonBuilder gsonBuilder = new GsonBuilder();
// register type adapters here, specify field naming policy, etc.
Gson Gson = gsonBuilder.create();
For instance, if our property name matches that of the JSON key, then we do not to annotate the attributes. However, if we have a different name we wish to
use, we can simply annotate the declaration with @SerializedName
:
public class BoxOfficeMovieResponse {
@SerializedName("movies")
List<Movie> movieList;
You can also use this same approach with enums. Suppose we had an enum of different colors:
public ColorTypes colorType;
public enum ColorTypes { RED, WHITE, BLUE };
We can annotate these attributes with @SerializedName
too:
@SerializedName("color")
public ColorTypes colorType;
public enum ColorTypes {
@SerializedName("red")
RED,
@SerializedName("white")
WHITE,
@SerializedName("blue")
BLUE
};
There is also the option to specify how Java field names should map to JSON keys by default. For instance, the Rotten Tomatoes API response includes an mpaa_rating
key in the JSON response. If we followed Java conventions and named this variable as mpaaRating
, then we would have to add a SerializedName
decorator to map them correctly:
public class BoxOfficeMovieResponse {
@SerializedName("mpaa_rating")
String mpaaRating;
}
An alternate way, especially if we have many cases similar to this one, is to set the field naming policy in the Gson library. We can specify that all field names should be converted to lower cases and separated with underscores, which would caused camel case field names to be converted from mpaaRating
to mpaa_rating
:
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
Gson gson = gsonBuilder.create();
If we know what date format is used in the response by default, we also specify this date format. The Rotten Tomatoes API for instance returns a release date for theaters (i.e. "2015-08-14"). If we wanted to map the data directly from a String to a Date object, we could specify the date format:
public String DATE_FORMAT = "yyyy-MM-dd";
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setDateFormat(DATE_FORMAT);
Gson gson = gsonBuilder.create();
Similarly, the date format could also be used for parsing standard ISO format time directly into a Date object:
public String ISO_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZ";
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setDateFormat(ISO_FORMAT);
Gson gson = gsonBuilder.create();
To enable the library to build a map what keys in the JSON data should match your internal variables, you can add annotations to provide the actual corresponding JSON name. An example of a previously defined Java model using the based on GitHub's User model is shown below. Note how the @SerializedName
annotation is used.
@SerializedName("login")
private String mLogin;
@SerializedName("id")
private Integer mId;
@SerializedName("avatarUrl")
private String mAvatarUrl;
By default, the Gson library is not aware of many Java types such as Timestamps. If we wish to convert these types, we can also create a custom deserializer class that will handle this work for us.
Here is an example of a deserializer that will convert any JSON data that needs to be converted to a Java field declared as a Timestamp:
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import java.lang.reflect.Type;
import java.sql.Timestamp;
public class TimestampDeserializer implements JsonDeserializer<Timestamp> {
public Timestamp deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
return new Timestamp(json.getAsJsonPrimitive().getAsLong());
}
}
Then we simply need to register this new type adapter and enable the Gson library to map any JSON value that needs to be converted into a Java Timestamp.
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Timestamp.class, new TimestampDeserializer());
Gson Gson = gsonBuilder.create();
We can use any type of HTTP client library with Gson, such as Android Async HTTP Client or Square's Retrofit library.
String apiKey = "YOUR-API-KEY-HERE";
AsyncHttpClient client = new AsyncHttpClient();
client.get(
"http://api.rottentomatoes.com/api/public/v1.0/lists/movies/box_office.json?apikey=" + apiKey,
new TextHttpResponseHandler() {
@Override
public void onFailure(int i, org.apache.http.Header[] headers, String s,
Throwable throwable) {
}
@Override
public void onSuccess(int i, org.apache.http.Header[] headers, String s) {
Response response = Response.fromJson(s);
}
});
public static Response fromJson(String response) {
Response movies = gson.fromJson(response, Response.class);
return movies;
}
}
We first need to define an interface file called RottenTomatoesService.java
. To ensure that the API call will be made asynchronously, we also define a callback interface. Note that if this API call required other parameters, we should always make sure that the Callback
declaration is last.
public interface RottenTomatoesService {
@GET("/lists/movies/box_office.json")
public void listRepos(Callback<BoxOfficeMovieResponse> responseCallback);
}
Then we can make sure to always inject the API key for each request by defining a RequestInterceptor
. class. In this way, we can avoid needing to have it be defined for each API call.
String apiKey = "YOUR-API-KEY-HERE";
RequestInterceptor requestInterceptor = new RequestInterceptor() {
@Override
public void intercept(RequestFacade request) {
request.addQueryParam("apikey", apiKey);
}
};
Next, we need to create a RestAdapter
and making sure to associate the adapter to this RequestInterceptor
:
RestAdapter restAdapter = new RestAdapter.Builder()
.setRequestInterceptor(requestInterceptor)
.setEndpoint("http://api.rottentomatoes.com/api/public/v1.0")
.build();
We then simply need to create a service class that will enable us to make API calls:
RottenTomatoesService service = restAdapter.create(RottenTomatoesService.class);
service.listRepos(new Callback<BoxOfficeMovies>() {
@Override
public void success(BoxOfficeMovies boxOfficeMovies, Response response) {
// handle response here
}
@Override
public void failure(RetrofitError error) {
}
});