Using a Custom Serializer

atuttle edited this page Nov 14, 2014 · 2 revisions
Clone this wiki locally

This page describes how to use a custom class to serialize your return data. Using a custom serializer will allow you to serialize data to formats other than JSON, the default/included serializer. For example, you could write a custom serializer that allows you to serialize data to XML, WDDX, YAML, RSS, plain text, HTML, or pretty much any other format you can think of; including custom, binary, or proprietary formats.

Writing a Custom Serializer

To support non-default return formats, you need to create your own Serializer. For example, let's say you wanted to support a YML return format. Create a new component (either in your /resources folder or in your external bean factory), with the following code:

<cfcomponent output="false" extends="taffy.core.baseSerializer">

    <cffunction name="getAsYml" access="public" output="false" taffy:mime="application/yml">
        <cfreturn serializeYml(variables.data) />
    </cffunction>

</cfcomponent>

Implementing a serializeYml() method is left as an exercise for the reader. Hint: look for an existing library, like SnakeYaml.

Supporting Multiple return formats

Suppose your api needs to be able to return both JSON and YML representations of your data. You would write your representation class like this:

<cfcomponent output="false" extends="taffy.core.baseSerializer">

    <cffunction name="getAsYml" access="public" output="false" taffy:mime="application/yml">
        <cfreturn serializeYml(variables.data) />
    </cffunction>

    <cffunction name="getAsJson" access="public" output="false" taffy:mime="application/json,text/json">
        <cfreturn serializeJson(variables.data) />
    </cffunction>

</cfcomponent>

Note that both methods go into the same serializer. Your serializer should be capable of serializing to each return format you want to support.

Overriding the default representation class

You tell Taffy to use your serializer by supplying either its Bean Name or CFC Dot Notation Path in the setting variables.framework.serializer. See the List of all variables.framework settings for more information.

A word of caution

Serializers are treated as Transient objects in Taffy, meaning that a new response object is instantiated for every request, which has both positive and negative side-effects.

On the positive side, there is no chance that wires will get crossed and the response to Request A will be sent for Request B because you forgot to var-scope something. On the other hand, it also means that your representation class should be as lightweight as possible to speed instantiation time (and your API's performance may suffer if you don't follow this advice). If you are using a library to do serialization (perhaps you're using JSONUtil for JSON, or AnythingToXML for XML) then you should cache the worker object in a persistent scope like Application or Server, because the library does not need to be re-instantiated on every request. Doing so will definitely help the performance of your API.

If you manage your serializer class with an external bean factory (like ColdSpring or DI/1) ensure that it is treated as a transient.

Getting all advanced up in here (requirements)

These are the things that your custom serializer must implement in order to interact with Taffy, and why:

  • setData(any data) - used to tell the class what data it has to serialize. The data parameter should accept any data type, and the function should return the this object to allow for method chaining.

  • noData() - used to return a (more or less) empty serializer object (status information with no result data). Should return the this object to allow for method chaining.

  • getAsX() - Used to get the serialized version of the data to be returned to the user. X will correspond with the available formats for your API. If you provide XML, JSON, and YAML mime types as options, your custom serializer should implement all three of the following: getAsXml, getAsJson, and getAsYaml. Note that these do not go into different classes - the one class must be able to serialize the data into all available formats.

  • withStatus(numeric statusCode) - used to set the return http status code. Should return the this object to allow for method chaining.

  • getStatus() - used by the framework to get the http status code to return to the consumer.

  • withHeaders(struct headers) - used to set additional return http headers. Should return the this object to allow for method chaining.

  • getHeaders() - used by the framework to get the custom http headers to add to the response, if any. Returns the headers structure from withHeaders().

You can inherit most of this functionality by extending taffy.core.baseSerializer. This class has all of the helper methods (eg. setData, noData, withStatus, withHeaders, etc), but no serialization methods (eg. getAsJson).

For example, if you wanted to recreate the included class taffy.core.nativeJsonSerializer, you could create your own representation class with the following code:

<cfcomponent output="false" extends="taffy.core.baseSerializer">

    <cffunction name="getAsJson" access="public" output="false" taffy:mime="application/json" taffy:default="true">
        <cfreturn serializeJson(variables.data) />
    </cffunction>

</cfcomponent>