Skip to content

Serializing Request Content

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

A request body may carry a payload in a format which is expected by the server. Common standardized formats are defined by Internet Media Types (also known as MIME types or Content-Types). These are recognized by a special identifier written as type/subtype, for example application/json, application/xml. A serializer is responsible for converting a given model (the payload) to an expected format which might be standardized or customized.


####1. Attaching a Serializer

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

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

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

    @PUT("/json")
    void putJsonContent(@Entity Content content);
    
    @PUT("/xml")
    @Serialize(XML)
    void putXmlContent(@Entity Content content);
}

####2. Using Prefabricated Serializers

Out-of-the-box serializers 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 serialization and Simple-XML is required for XML serialization. If these two libraries are not detected on the build-path, the JSON and XML serializers will be disabled and any usage will result in an informative warning.


Serializers for `application/json` and `application/xml` work by converting a model into a JSON or XML `String`. For JSON, unless a custom [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 will be used in the JSON string. Likewise for XML, if [explicit element and attribute naming](http://simple.sourceforge.net/download/stream/doc/tutorial/tutorial.php) is not used, the variable names of the class will be used for the XML elements.
public class Issue {

    private String title;
    private String body;
    private String assignee;
    private List<String> labels;

    ...
}

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

    @POST("/repos/sahan/RoboZombie/issues")
    void postIssue(@Entity Issue issue);
}

...

Issue issue = new Issue();
issue.setTitle("Update Wiki");
issue.setBody("Revise wiki pages.")
issue.setAssignee("sahan");
issue.setLabels(Arrays.asList("documentation", "backlog"));

gitHubEndpoint.postIssue(issue);

request-body: {"title": "Update Wiki", "body": "Revise wiki pages.", "assignee": "sahan", "labels": ["documentation", "backlog"]}


Attaching a serializer for `text\plain` via `@Serialize(PLAIN)` will simply invoke `String.valueOf(...)` on the given model. Use `@Serialize(PLAIN)` on requests when you need to override a serializer attached at the endpoint level.
@Serialize(JSON)
@Endpoint("https://example.com")
public interface ExampleEndpoint {

    @PUT("/json")
    void putModel(@Entity Content content);
    
    @PUT("/json")
    @Serialize(PLAIN)
    void putJson(@Entity StringBuilder json);
}

####3. Creating Custom Serializers

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

Note that all serializers 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.Heap;

final class HeapSerializer extends AbstractSerializer<Heap, String> {

    public HeapSerializer() {

        super(String.class);
    }

    @Override
    protected String serialize(InvocationContext context, Heap heap) {
        
        return heap.deflate();
    }
}

You might even extend AbstractSerializer to create your own JSON serializer with a custom Gson instance.

import some.third.party.lib.Model;

final class CustomJsonSerializer extends AbstractSerializer<Object, String> {

    private final Gson gson;

    public CustomJsonSerializer() {

        super(String.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 String serialize(InvocationContext context, Object input) {
        
        Type type = TypeToken.get(input.getClass()).getType();
        return gson.toJson(input, type);
    }
}

Custom serializers are attached by specifying their Class on the type property, e.g. @Serialize(type = CustomSerializer.class).

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

    @PUT("/json")
    void putJsonContent(@Entity Content content);
    
    @PUT("/heap")
    @Serialize(type = HeapSerializer.class)
    void putHeap(@Entity Heap heap);
}