Skip to content

Deserializing Response Content

Lahiru Sahan Jayasinghe edited this page Dec 31, 2013 · 8 revisions

The response body may contain content in numerous formats identified by content-types. A deserializer is responsible for parsing the response content into model which can be readily consumed by your application code.


####1. Attaching a Deserializer

Deserializers are attached using an @Deserialize annotation which takes the ContentType to identify the proper deserializer to be used. This annotation can be placed either on an endpoint or a request. If placed on an endpoint, the deserializer is used for all requests defined on the endpoint. If a specific request requires an alternate deserializer, the root definition can be overridden with another @Deserialize annotation on the request.

import static com.lonepulse.robozombie.annotation.Entity.ContentType.JSON;
import static com.lonepulse.robozombie.annotation.Entity.ContentType.XML;

@Deserialize(JSON)
@Endpoint("https://example.com")
public interface ExampleEndpoint {

    @GET("/json")
    Content getJsonContent();
    
    @GET("/xml")
    @Deserialize(XML)
    Content getXmlContent();
}

####2. Using Prefabricated Deserializers

Out-of-the-box deserializers are provided for the content-types text/plain, application/json and application/xml. These can be used for plain text, JSON or XML content respectively.

Note that Gson is required for JSON deserialization and Simple-XML is required for XML deserialization. If these two libraries are not detected on the build-path, the JSON and XML deserializers will be disabled and any usage will result in an informative warning.


Deserializers for `application/json` and `application/xml` work by converting the response body into a model. The model will be an object of a class which represents the JSON or XML. For JSON, unless a [field naming policy](https://sites.google.com/site/gson/gson-user-guide#TOC-JSON-Field-Naming-Support) is used, the variable names of the class should be equal to that of the JSON object. Likewise for XML, if the element names aren't equal to the class variables, [explicit element and attribute naming](http://simple.sourceforge.net/download/stream/doc/tutorial/tutorial.php) can be used.
public class Repo {

    private String id;
    private String name;
    private boolean fork;
    private int stargazers_count;

    ...
}

@Deserialize(JSON)
@Endpoint("https://api.github.com")
public interface GitHubEndpoint {


    @GET("/users/{user}/repos")
    List<Repo> getRepos(@PathParam("user") String user);
}

...

List<Repo> repos = gitHubEndpoint.getRepos("sahan");

response-body: [{ "id": 7576372, "name": "RoboZombie", "fork": false, ... ]


Attaching a deserializer for `text\plain` via `@Deserialize(PLAIN)` will give you the raw response body. This is essentially the same as defining the method return type as a `CharSequence`. Use `@Deserialize(PLAIN)` on requests when you need to override a deserializer attached at the endpoint level.
@Deserialize(JSON)
@Endpoint("https://example.com")
public interface ExampleEndpoint {

    @GET("/json")
    Content getModel();
    
    @GET("/json")
    @Deserialize(PLAIN)
    String getJson();
}

####3. Creating Custom Deserializers

If an out-of-the-box deserializer is not a good match, you're free to create your own custom deserializer by extending AbstractDeserializer and overriding deserialize(...). This extension should be parameterized to the output type and the default constructor should be exposed which calls the super constructor with the Class of the output type.

Note that all deserializers exist as singletons and are expected to be thread-safe. If an incurred state affects thread-safety, ensure that proper thread-local management is performed.

import org.apache.http.util.EntityUtils;

import some.third.party.lib.Document;

final class DocumentDeserializer extends AbstractDeserializer<Document> {

    public DocumentDeserializer() {

        super(Document.class);
    }

    @Override
    protected Document deserialize(InvocationContext context, HttpResponse response) {
        
        String customFormattedContent = EntityUtils.toString(response.getEntity());
        return Document.parse(customFormattedContent);
    }
}

You might even extend AbstractDeserializer to create your own JSON deserializer with a custom Gson instance.

import some.third.party.lib.Model;

final class CustomJsonDeserializer extends AbstractDeserializer<Object> {

    private final Gson gson;

    public CustomJsonDeserializer() {

        super(Object.class);

        this.gson = new GsonBuilder()
        .serializeNulls()
        .excludeFieldsWithModifier(Modifier.STATIC)
        .setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
        .registerTypeAdapter(Model.class, new InstanceCreator<Model>() {

            @Override
            public Model createInstance(Type type) {

                return Model.newInstance(); //default constructor unavailable
            }
        })
        .create();
    }

    @Override
    protected Object deserialize(InvocationContext context, HttpResponse response) {
        
        String json = EntityUtils.toString(response.getEntity());
        Type type = TypeToken.get(context.getRequest().getReturnType()).getType();

        return gson.fromJson(json, type);
    }
}

Custom deserializers are attached by specifying their Class on the type property, e.g. @Deserialize(type = CustomDeserializer.class).

@Endpoint("https://example.com")
@Deserialize(type = CustomJsonDeserializer.class)
public interface ExampleEndpoint {

    @GET("/json")
    Content getJsonContent();
    
    @GET("/document")
    @Deserialize(type = DocumentDeserializer.class)
    Document getDocument();
}