Blog

- March 23, 2016

The Restlet Framework integrates a powerful feature called converters to work on custom objects in server resources and let the framework convert them to and from text payloads. In this context, one of the most used converters is the Jackson one. The converter supports several format types like JSON, XML, YAML and Smile.

Whereas most of the times using the converter with default settings is enough, you can have some cases where you want to precisely control the structure of the output content. In this article, we will describe how to implement such use cases.
Getting started with the Jackson converter
In this section, we will describe the foundations of the Jackson converter and how to use it within a Restlet Framework application.

Installing the Jackson converter

The Restlet Framework supports automatic detection and registration for converters. Putting the JAR file corresponding to the org.restlet.ext.jackson extension will automatically register the Jackson converter into the Restlet Framework engine.

Using the Jackson converter

To use this converter, you don’t need to do anything special. We can use classes directly within annotated methods of server resources for parameters and return. In this case, the Restlet Framework will automatically select the right available converter to:

  • Create a text response payload by serializing the returned object
  • Instantiate and deserialize the received content in the case of methods like POST, PUT or PATCH.

Here is a sample use of this approach:

public class TableServerResource {
    @Get
    public TableResponse getTable() {
        (...)
    }
}

Converters can also be used within clients based on annotated interfaces. In this case, the wrap method must be used from the ClientResource class. The annotated interface for the previous server resource will look like:

public interface TableResource {
    @Get
    TableResponse getTable();
}

We can leverage it when using the ClientResource class to call the server resource:

ClientResource cr = new ClientResource("http://...");
TableResource resource = cr.wrap(TableResource.class);
TableResponse responseContent = resource.getTable();

Now we have described the context, we will dig into more details about how to customize the generated payload using Jackson. Let’s start with the use of Jackson annotations.

Leveraging Jackson annotations

Jackson provides a set of annotations to put into the class to configure the way corresponding contents will be generated

  • JsonProperty is used to indicate external property name, name used in data format (JSON or one of other supported data formats)
  • JsonIgnore is a simple annotation to use for ignoring specified properties
  • JsonInclude is used to define if certain “non-values” (nulls or empty values) should not be included when serializing; can be used on per-property basis as well as default for a class

Jackson also provides a set of annotations in the case for specific formats like XML. They allow you to configure the output for such format:

  • JacksonXmlRootElement: to specify the XML element to use for wrapping the root element
  • JacksonXmlElementWrapper: to specify the XML element to use for wrapping List and Map properties
  • JacksonXmlProperty: to specify the XML namespace and local name for a property; as well as whether property is to be written as an XML element or an attribute.

Here is a sample of use of these annotations in the TableResponse class:

@JacksonXmlRootElement( localName = "table")
@JsonInclude( JsonInclude.Include.NON_NULL)
public class TableResponse {
    protected List data;
    protected String[] columns;

    public TableResponse(String[] columns, List<List<String>> data ) {
        this.columns = columns;
        this.data = data;
    }

    @JacksonXmlElementWrapper(localName = "data")
    @JacksonXmlProperty(localName = "row")
    public List<List<String>> getData() {
        return data;
    }

    @JacksonXmlElementWrapper(localName = "columns")
    @JacksonXmlProperty(localName = "column")
    public String[] getColumns() {
        return columns;
    }
}

With this approach, we can have some control on the generated output but it’s not enough for advanced use cases, specially if we need to handle structures in a finer way and do specifics for JSON or XML.

Using a custom serializer

In some cases, Jackson annotations might not be enough and don’t allow us to have the full control of the output. Imagine we want to finely control generated outputs for different content types like XML and JSON.

For example, the following contents can’t be created by only leveraging classes and Jackson annotations.

outputs

Let’s create our custom serializer to generate such contents based on the TableResponse class.

Implementing a custom serializer

The first step consists of creating a class that extends the StdSerializer class of Jackson. After adding required constructors, we need to override the serialize method. This method is responsible for actually creating the structure of the generated content.

The following snippet describes the skeleton of a TableResponseSerializer class that corresponds to a custom Jackson serializer:

public class TableResponseSerializer extends StdSerializer {
    private MediaType mediaType;

    public TableResponseSerializer(MediaType mediaType) {
        super(TableResponse.class);
        this.mediaType = mediaType;
    }

    private void serializeJson(TableResponse swe, 
            JsonGenerator jgen,
            SerializerProvider sp) throws IOException, JsonGenerationException {
        (...) 
    }

    private void serializeXml(TableResponse swe, 
            JsonGenerator jgen,
            SerializerProvider sp) throws IOException, JsonGenerationException {
       (...)        
    }

    @Override
    public void serialize(TableResponse swe, 
                      JsonGenerator jgen,
                      SerializerProvider sp) throws IOException, JsonGenerationException {
        if (mediaType.equals(MediaType.APPLICATION_JSON)) {
            serializeJson(swe, jgen, sp);
        } else if (mediaType.equals(MediaType.TEXT_XML)) {
            serializeXml(swe, jgen, sp);
        }
    }
}

Since we want to finely control the generated structure for both JSON and XML contents, we defined separate methods: serializeJson for JSON and serializeXml for XML.

As you can see, the serialize method accepts a parameter of type JsonGenerator. This class provides a set of methods to define elements and their contents. Here are some of them:

  • writeArrayFieldStart: create a field of type array for an object property
  • writeObjectFieldStart: create a field of type object for an object property
  • writeStartArray: create an array
  • writeStartObject: create an object
  • writeString: write a string value for the current element
  • writeEndArray: end the content of the current array
  • writeEndObject: end the content of the current array

Based on these methods, we can implement the serializeJson method:

private void serializeJson(TableResponse swe, 
        JsonGenerator jgen,
        SerializerProvider sp) throws IOException, JsonGenerationException {
    jgen.writeStartObject();      

    // Data
    jgen.writeArrayFieldStart("data");
    for (List<String> row : swe.getData()) {
        jgen.writeStartArray();
        for (String rowElt : row) {
            jgen.writeString(rowElt);
        }
        jgen.writeEndArray();
    }
    jgen.writeEndArray();

    // Columns
    jgen.writeArrayFieldStart("columns");
    for (String column : swe.getColumns()) {
        jgen.writeString(column);
    }
    jgen.writeEndArray();

    jgen.writeEndObject();
}

And the serializeXml one:

private void serializeXml(TableResponse swe, 
        JsonGenerator jgen,
        SerializerProvider sp) throws IOException, JsonGenerationException {
    jgen.writeStartObject();

    // Data
    jgen.writeObjectFieldStart("data");
    jgen.writeArrayFieldStart("row");
    for (List<String> row : swe.getData()) {
        jgen.writeStartObject();
        jgen.writeArrayFieldStart("value");
        for (String rowElt : row) {
            jgen.writeString(rowElt);
        }
        jgen.writeEndArray();
        jgen.writeEndObject();
    }
    jgen.writeEndArray();
    jgen.writeEndObject();

    // Columns
    jgen.writeObjectFieldStart("columns");
    jgen.writeArrayFieldStart("column");
    for (String column : swe.getColumns()) {
        jgen.writeString(column);
    }
    jgen.writeEndArray();
    jgen.writeEndObject();

    jgen.writeEndObject();
}

Now we have implemented the custom converter, we need to register it within the Restlet Framework engine.

Registering the serializer

To register our custom serializer, we need to have access to the ObjectMapper instance of Jackson used to actually handle the object conversion.

Internally one instance of ObjectMapper is used per JacksonRepresentation. To be able to configure it, we need to extend the JacksonRepresentation class and override the createObjectMapper method to configure the newly created instance.

The following CustomJacksonRepresentation class describes how to implement this approach:

public class CustomJacksonRepresentation<T> extends JacksonRepresentation<T> {
    public CustomJacksonRepresentation(Representation representation,
                     Class<T> objectClass) {
        super(representation, objectClass);
    }

    public CustomJacksonRepresentation(T object) {
        super(object);
    }

    @Override
    protected ObjectMapper createObjectMapper() {
        ObjectMapper objectMapper = super.createObjectMapper();

        if (getObjectClass().equals(TableResponse.class)) {
            SimpleModule module = new SimpleModule("table response module");    
            module.addSerializer(new TableResponseSerializer(getMediaType())); 
            objectMapper.registerModule(module);
        }

        return objectMapper;
    }
}

In order to use the CustomJacksonRepresentation class, we need to extend the JacksonConverter class. The create methods must be overridden to use this kind of representation instead of the default JacksonRepresentation one.

public class CustomJacksonConverter extends JacksonConverter {
    protected <T> JacksonRepresentation<T> create(MediaType mediaType, T source) {
        return new CustomJacksonRepresentation<T>(mediaType, source);
    }

    protected <T> JacksonRepresentation<T> create(Representation source,
         Class<T> objectClass) {
    return new CustomJacksonRepresentation<T>(source, objectClass);
}

The last step consists of registering this converter to be able to use it when returning an object from the server resource methods.

Registering the custom converter

To register the CustomJacksonConverter class, you can rely on the getRegisteredConverters of the Engine before starting your component. This method returns the list of registered converters that can be used for object conversions.

List<ConverterHelper> converters = Engine.getInstance().getRegisteredConverters();
JacksonConverter jacksonConverter = new JacksonConverter();
for (ConverterHelper converter : converters) {
    if (converter instanceof JacksonConverter) {
        jacksonConverter = (JacksonConverter) converter;
        break;
    }
}

if (jacksonConverter!=null) {
    converters.remove(jacksonConverter);
    converters.add(new CustomJacksonConverter());
}

Don’t forget to remove the JacksonConverter registered by default. Otherwise this converter could be used instead of our custom one.

Conclusion

As we can see in this article, it’s completely possible to leverage converters provided by the Restlet Framework, in our case the Jackson one, to create the output content of HTTP calls and handle input ones. Resources can directly use representation classes and don’t worry about serialization and deserialization. They are handled under the hood by converters.

This approach provides a clean separation of concerns, since server resources only work with classes corresponding to representation structures. If some tweaking is required to get the exact needed contents, we don’t need to update these resources but we can customize used converters.