Example: Array of CFC Serialization
The following example illustrates serializing array of CFC to XML and JSON formats.
Create an array of arrayCFCdefinition.cfc:
<cfcomponent>
<cfproperty name="str" type="string"/>
</cfcomponent>
|
arrayCFC.cfc produces the required array as follows:
<cfcomponent restpath="arrayOfCFC">
<cffunction name="func1" access="remote" output="false" returntype="arrayCFCdefinition[]"
httpmethod="get" produces="text/xml">
<cfset arrCFC = arraynew(1)>
<cfloop from=1 to=2 index="i">
<cfset obj = createObject("component", "arrayCFCdefinition")>
<cfset obj.str = i>
<cfset arrayAppend(arrCFC, obj)>
</cfloop>
<cfreturn arrCFC>
</cffunction>
<cffunction name="func2" access="remote" output="false" returntype="arrayCFCdefinition[]"
httpmethod="get" produces="text/json">
<cfset arrCFC = arraynew(1)>
<cfloop from=1 to=2 index="i">
<cfset obj = createObject("component", "arrayCFCdefinition")>
<cfset obj.str = i>
<cfset arrayAppend(arrCFC, obj)>
</cfloop>
<cfreturn arrCFC>
</cffunction>
</cfcomponent>
|
- Do the following to access the resource:
For XML:
<cfhttp url="http://127.0.0.1:8500/rest/RestTest/arrayOfCFC" method="get" result="res1">
<cfhttpparam type="header" name="accept" value="text/xml">
</cfhttp>
|
For JSON:
<cfhttp url="http://127.0.0.1:8500/rest/RestTest/arrayOfCFC" method="get" result="res2">
<cfhttpparam type="header" name="accept" value="text/json">
</cfhttp>
|
- You receive the following serialized output as response:
For XML:
<array id="1" size="2" type="cfsuite.restservices.restservices.new.ArrayCFCdefinition">
<item index="1" type="COMPONENT">
<component id="2" name="cfsuite.restservices.RESTServices.New.arrayCFCdefinition">
<property name="STR" type="NUMBER">
1.0
</property>
</component>
</item>
<item index="2" type="COMPONENT">
<component id="3" name="cfsuite.restservices.RESTServices.New.arrayCFCdefinition">
<property name="STR" type="NUMBER">
2.0
</property>
</component>
</item>
</array>
|
For JSON:
Example: Array of CFC: Deserialization
The following example illustrates deserializing array of CFC from XML format.
Note: Deserializing array of CFC is unsupported for JSON.
|
Create an array of arrayCFCdefinition.cfc:
<cfcomponent>
<cfproperty name="str" type="string"/>
<cffunction name="check" returntype="any">
<cfreturn this.str>
</cffunction>
</cfcomponent>
|
arrayCFC.cfc produces the required array as follows:
<cfcomponent>
<cffunction name="func3" access="remote" output="false" returntype="string"
httpmethod="put" produces="text/xml" consumes="text/xml">
<cfargument name="arg" type="arrayCFCdefinition[]"/>
<cfif arg[2].check() eq "2">
<cfreturn "true">
<cfelse>
<cfreturn "false">
</cfif>
</cffunction>
</cfcomponent>
|
Do the following to access the resource for XML:
<cfhttp url="http://127.0.0.1:8500/rest/RestTest/arrayOfCFC" method="put" result="res3">
<cfhttpparam type="header" name="content-type" value="text/xml">
<cfhttpparam type="header" name="accept" value="text/xml">
<cfhttpparam type="body" value="<ARRAY ID=""1"" SIZE=""2"" TYPE=""cfsuite.restservices.restservices.new.ArrayCFCdefinition""><ITEM INDEX=""1"" TYPE=""COMPONENT""><COMPONENT ID=""2"" NAME=""cfsuite.restservices.restservices.new.ArrayCFCdefinition""><PROPERTY NAME=""STR"" TYPE=""NUMBER"">1.0</PROPERTY></COMPONENT></ITEM><ITEM INDEX=""2"" TYPE=""COMPONENT""><COMPONENT ID=""3"" NAME=""cfsuite.restservices.restservices.new.ArrayCFCdefinition""><PROPERTY NAME=""STR"" TYPE=""NUMBER"">2.0</PROPERTY></COMPONENT></ITEM></ARRAY>">
</cfhttp>
|
- Refer to the function. You are verifying the value of the property of arrayCFC definition.cfc for the second index of the array.
string, boolean, numeric, binary, and date
Specify the values directly in the body of the request.
Handling cyclic dependency
In ColdFusion, cyclic dependency is handled using the ID reference. All ColdFusion complex data types have unique IDs when serialized. If the same object has to be serialized elsewhere, instead of serializing the object again, a reference is made to the already serialized data using its ID. In the following example, the main object is a struct. The struct contains an array of objects. The array has two elements and both the elements are the same instance of a struct. During serialization, the first element in the array is serialized as it is. The ID of the serialized struct is 2. Instead of serializing the second element, as that object is already serialized, IDREF attribute is used to refer to the already serialized struct instance.
<struct id="1">
<entry name="arrayinastruct" type="array">
<array id="2" size="2">
<item index="0" type="struct">
<struct id="3">
<entry name="name" type="string">joe</entry>
<entry name="age" type="string">30</entry>
<entry name="address" type="string">101 some drive, pleasant town, 90010</entry>
<entry name="eyecolor" type="string">blue</entry>
<entry name="income" type="string">50000</entry>
</struct>
</item>
<item index="1" type="struct">
<struct idref="3"/>
</item>
</array>
</entry>
</struct>
|
Note: Object reference is taken care of by ColdFusion at the time of deserialization also.
|
JSON serialization and REST services
Serialization specifications
- The content type of the Accept header of the request has to be text/JSON, application/JSON, or text/plain.
- REST service should have a function that can handle the required MIME types.
- Function has to return any of the ColdFusion supported data types other than binary.
Cyclic behavior is unsupported. But in the case of arrays, you might see the serialized string published, but not with expected output as explained in the following example:
<cfset this.arr1 = arrayNew(1)>
<cfset this.arr1[1] = "1">
<cfset this.arr1[2] = this.arr1>
<cfset this.arr1[3] = "3">
|
When an array is assigned to a variable (in this case) <cfset this.arr12 = this.arr1>, you assign arr1 as the item in the second index of arr1. ColdFusion implicitly creates a new array and copies the data from arr1 to the newly created array. The newly created array is assigned to the second index of arr1. Therefore, both the instances are different, and therefore cyclic dependency is impacted.When you serialize, you get the following output:
As you can observe, the inner array gets truncated after the first element.
Deserialization specifications
- The content of the request is in a predefined format specified by ColdFusion.
- The content type of the request is text/JSON, application/JSON, or text/plain.
- A function in the service consumes the MIME type of the request.
- cfargument does not have the attributes restargsource and restargname specified.
- cfargument type is ColdFusion supported data type other than binary or CFC definition.
- Only one argument has no restAargSource attribute specified. The whole body of the request is deserialized to the argument type.
- Cyclic behavior is unsupported. But in the case of cyclic arrays, you might see the deserialized array published, but not giving expected output.
Format definitions
Format for query
{'COLUMNS':['columnNameOne','columnNameTwo'],'Data':[['value one','value two'],['444.0','value four']]}
|
Format for struct
{'NAME':'joe','AGE':30,'ADDRESS':'101 Some Drive, Pleasant Town, 90010','EYECOLOR':'blue','INCOME':50000}
|
Format for component
{'NAME':'Paul','AGE':444.0,'DOB':'July, 27 2011 00:00:00'}
|
Note: Deserialization is unsupported for components.
|
Format for array
[{'NAME':'joe','AGE':30,'ADDRESS':'101 Some Drive, Pleasant Town, 90010','EYECOLOR':'blue','INCOME':50000},{'NAME':'paul','AGE':25,'ADDRESS':'Some other address','EYECOLOR':'black','INCOME':40000}]
|
string, boolean, numeric, and date
Specify the values directly in the body of the request. ----
Support for GZip encoding
If the request contains a Content-Encoding header of "gzip" then the request entity (if any) is uncompressed using the gzip algorithm. If the request contains an Accept-Encoding header containing "gzip" and an "If-None-Match" Header, entitytag value is checked: if it contains the -gzip suffix, remove this suffix, otherwise, completely remove the "if-none-match" header.
If the request contains a Accept-Encoding header that contains "gzip", then the response entity (if any) is compressed using gzip and a Content-Encoding header of "gzip" is added to the response. As this filter is active, the resource representation can be compressed. the value "Accept-Encoding" is so added to the Vary header. If any entityTag is used and content may be gzipped, the "-gzip" suffix is added to entitytag value.
Site-level REST application support
In ColdFusion 10, multiple applications cannot have the same name, even if the applications are residing in different hosts. With the enhanced ColdFusion 11 REST feature, you can have multiple applications with the same name but available in different hosts. Also, you can set one default application (containing the REST service) for each virtual host.
You can register the directory containing the REST service by using any one of the following options:
- autoregister Application Setting
- ColdFusion Administrator console
- ColdFusion Admin API
- restInitApplication method
Option 1: autoregister Application Setting
A new Application setting autoregister is introduced in ColdFusion 11.
- You can set the auto register to true if you want to enable the auto registration for the application:
<cfset this.restsettings.autoregister="true"/>
|
- Specifying the servicemapping is optional. If servicemapping is not specified, the "this.name"/application name will be taken as default.
<cfset this.restsettings.servicemapping="testmapping"/>
|
- Specify the usehost or hostname, If usehost attribute is set to true, then the host name is parsed from the URL. usehost="True" is the default value for REST service registration. If usehost=true, the host name will be taken from the URL. The host name will be used for registration. If no hosts are specified, that is, usehost="False", all applications without a host name and all requests from any host will be serviced.
<cfset this.restsettings.usehost=true/>
|
- Explicitly naming the host name will make the host name. If the host name is not mentioned, then the usehost name will be defaulted.
<cfset this.restsettings.host="www.adobe.com"/>
|
- Set isDefault to true and the application will be made as default app.
<cfset this.restsettings.isDefault=true/>
|
When the first request comes to the application and if that request is non-REST request, the application will be started and registered. If both usehost and host are not specified, the apps will be registered without host name.
|
Note: If the first request itself is a REST request, then the application will not get started. |
|
Option 2: Registering a REST application using ColdFusion Administrator console
- Use the Administrator console to register a directory containing REST-enabled CF components. Select Adobe ColdFusion 11 Administrator console > Data & Services > REST services.
- Browse and select the root path (or the application path) where ColdFusion will search for the directory containing a set of REST enabled CF components.
(Optional) In the Service Mapping section, specify the virtual mapping in place of application name. If the folder has an Application.cfc file and an application name, use the application name to identify the REST services in the directory.
(Optional) Enter the Host Name. This will enable the mapping of the selected application with the host. Each host can have a default application and multiple applications with the same application name can be mapped to different hosts.
- Set the REST application as a default application by selecting the option Set as default application. By selecting this option, you omit the need to specify the service mapping or the application name in the URI.
- Click the Add Service button to complete registration of the directory.
- View the Active ColdFusion REST Services display table to view the list of active service mappings. You can use the same to edit or delete the service mappings in future.
Option 3: Registering a REST application using the ColdFusion Admin API
You can use functions defined in CFIDE.adminapi.extensions CFC to manage a REST application. The functions are:
- registerRESTService(path[,serviceMapping[,host[,isdef]]]: This function registers the REST application. The root path specifies the directory containing REST-enabled CF component. Optionally, the service mapping for the REST application, host name, and isdefault can be specified.
- getRESTServices(): This function returns an array of REST services registered with the ColdFusion Administrator.
- deleteRESTService(rootPath): This function deletes the specified REST application registered with the ColdFusion Administrator.
- refreshRESTService(rootPath): If you make any changes to the REST-enabled CF component, you can refresh the registered application by calling this function.
- getDefaultRestService(): Returns the server wide default REST application.
- getAllDefaultRESTServices: Returns all the default REST services. It is an array of path – host pair.
Option 4: Registering a REST application using the restInitApplication method
You can also register a REST application by calling the method restInitApplication
The syntax is:
restInitApplication(rootPath[,serviceMapping[,options]])
|
The options are an optional argument. In the options struct you can pass the:
For registering by specifying the host explicitly:
<cfset str=structNew()>
<cfset str.host = "www.site1.com:82">
<cfset str.isDefault = "true">
<cfset RestInitApplication("C:\dev\ColdFusion\cf_main\cfusion\wwwroot\withhostAndDefault", "withhostAndDefault", str)>
App registered
|
For registering by specifying the UseHost attribute: The host name will be extracted from the request URL and will be used for registration.
<cfset str=structNew()>
<cfset str.useHost = "true">
<cfset str.isDefault = "true">
<cfset RestInitApplication("C:\dev\ColdFusion\cf_main\cfusion\wwwroot\withhostAndDefault", "withhostAndDefault", str)>
App registered
|
|
You do not need Administrator privileges to perform the task. The syntax is:
restInitApplication(rootPath[,serviceMapping[,options]])
|
- In the options you can specify host, useHost and isDefault. The option usage is same as autoRegister feature.
- If you have already registered the application using the Administrator module, call restInitApplication to refresh the REST service.
- Use the restDeleteApplication function to delete the REST service. The syntax is restDeleteApplication(rootPath)
Support for pluggable serializer and deserializer
In the application.cfc, you can register your own handler for serializing and deserializing the complex types. If the serializer is not specified, ColdFusion uses the default mechanism for serialization.
Example: If you have a phone directory to be serialized on the following parameters:
- The main data structure is an Array.
- Each element of the array is a struct.
- The Struct contains 2 elements.
- The first is the name of the contact and the second is the struct which contains the code and the phone number.
Array
Struct
[
name(String],
phoneNo(Struct)
[
code(String),
no(String)
]
]
Struct
[
name(String],
phoneNo(Struct)
[
code(String),
no(String)
]
]
|
And in this example, you want to serialize only struct in a simple format and want the result as follows:
<Array size="1"> <Item index="1"> <root> <Name>Paul</Name> <PhoneNo> <root> <Code>080</Code> <No>411150326</No> </root> </PhoneNo> </root> </Item> </Array>
|
With the enhanced ColdFusion 11 REST feature, the user can use the plugged-in custom serializer instead of using the default serialization function. The custom serializer has four functions:
- CanSerialize - Returns a boolean value and takes the "Accept Type" of the request as the argument. You can return true if you want the customserialzer to serialize the data to the passed argument type.
- Serialize - The main serialization logic must be implemented in this function. If canSerialize returns "True" for a request, then ColdFusion will use this function to serialize. If canSerialize returns false, then ColdFusion will use the default serialization to serialize.
- CanDeserialize - Returns a boolean value and takes the "Content Type" of the request as the argument. You can return true if you want the customserialzer to deserialize the data.
- DeSerialize - The main deserialization logic must be implemented in this function. If canDeSerialize returns "True" for a request, then ColdFusion will use this function to deserialize. If canDeSerialize returns false, then ColdFusion will use the default de-serialization to deserialize.
The below CustomSerializer code sample will help you to achieve the result for the scenario explained above (Customizing serialization/deserialization of phone directory):
<cfcomponent>
<cffunction name="serialize" access="remote" returntype="String">
<cfargument name="arg" type="any" hint="The object to be serialized"/>
<cfargument name="type" type="string"
hint="The accept header of the request in array format. The order of types in the array is according to the priority of the MIME types in the header."/>
<cfset var result = "">
<cfset var key = "">
<cfif arguments.type eq "XML">
<cfif isStruct(arguments.arg)>
<cfset result = "<root>">
<cfloop collection="#arguments.arg#" item="key">
<cfset result = result & "<" & key & ">">
<cfset result = result & serializeXML(arguments.arg[key], true)>
<cfset result = result & "</" & key & ">">
</cfloop>
<cfset result = result & "</root>">
<cfreturn result>
<cfelse>
<!--- SerializeXML is a new function added in ColdFusion 11. This function will serialize the object to XML
using ColdFusion's default serialization mechanism." --->
<cfreturn serializeXML(arguments.arg)>
</cfif>
<cfelseif arguments.type eq "JSON">
<cfdump var="#arguments.arg.getClass().getName()#" output="console">
<cfif arguments.arg.getClass().getName() eq "java.lang.String">
<cfreturn "test" & arguments.arg>
<cfelse>
<cfreturn serializeJSON(arguments.arg)>
</cfif>
<cfelse>
<!--- Serialize is a new function added in ColdFusion 11. This function will serialize the object to a
a specified type using ColdFusion's default serialization mechanism. --->
<cfreturn serialize(arguments.arg, arguments.type)>
</cfif>
</cffunction>
<cffunction name="canSerialize" access="remote" returntype="boolean">
<cfargument name="type" type="string"/>
<cfif arguments.type eq "XML">
<cfreturn true>
<cfelseif arguments.type eq "JSON">
<cfreturn true>
<cfelse>
<cfreturn false>
</cfif>
</cffunction>
<cffunction name="canDeserialize" access="remote" returntype="boolean">
<cfargument name="type" type="string"/>
<cfif arguments.type eq "XML">
<cfreturn true>
<cfelseif arguments.type eq "JSON">
<cfreturn true>
<cfelse>
<cfreturn false>
</cfif>
</cffunction>
<cffunction name="deserialize" access="remote" returntype="any">
<cfargument name="arg" type="String" hint="The string to be deserialized"/>
<cfargument name="type" type="String" hint="The content-type header of the request."/>
<cfset var xmlDoc = "">
<cfset var result = "">
<cfset var numEntries = "">
<cfset var key = "">
<cfset var value = "">
<cfif arguments.type equals "XML" and isXml(arguments.arg)>
<cfset xmlDoc = xmlParse(arguments.arg)>
<cfif xmlDoc.XmlRoot.XmlName equals "root">
<cfset result = StructNew()>
<cfset numEntries = ArrayLen(xmlDoc.root.XMLChildren)>
<cfloop index="i" from="1" to="#numEntries#">
<cfset key = xmlDoc.root.XMLChildren[i].XmlName>
<cfif ! len(Trim(xmlDoc.root.XMLChildren[i].XMLText))>
<cfset value = deserializeXML(ToString(xmlDoc.root.XMLChildren[i].XMLChildren[1]), true)>
<cfelse>
<cfset value = deserializeXML(xmlDoc.root.XMLChildren[i].XMLText, true)>
</cfif>
<cfset result[key] = value>
</cfloop>
<cfreturn result>
<cfelse>
<cfreturn deserializeXML(arguments.arg, true)>
</cfif>
<cfelse>
<cfreturn deserializeXML(arguments.arg, true)>
</cfif>
</cffunction>
</cfcomponent>
|
The CustomSerializer that you have implemented can be specified through application.cfc.
<cfset this.customSerializer="CustomSerializer">
|