Recently, I was building some ORM Entities with many to one relationships, and these entities' data needed to be serialized to JSON in order to be used by some JavaScript. Unfortunately, there is a known bug in ColdFusion 9.0.1+ (And rumor has it that it exists in CF10 Final) where using serializeJSON() on an entitiy, it will only serialize the first two entities in a many to one relationship, and the rest as empty structs.

Missing entities that are mapped to another by a relationship is a a major blocker, so after a little research, I came across a pair of bugs on the Adobe CF9 and CF10 Preview trackers, as well as a blog post that detailed the issue, but none of these had a workaround. And even more importantly, there is no EntityToStruct'esque method (that I could find), to attempt a workaround with. Thankfully however, ColdFusion exposes a method called GetMetadata(), which will do exactly what it says -- retrieve the metadata for any object passed to it as a parameter.

The resulting metadata contains something very important when used on an ORM entity -- a listing of all of the properties. Now, I was getting somewhere! But attempting to access these raw properties will result in an exception -- they are private! Have no fear though, since the ORM automatically generates getters and setters, the method names to call those getters and setters follow an extremely simple pattern -- get[property name here](). By calling these methods using cfinvoke, I could access the value for each of the properties.

Now that all the puzzle pieces were on the table, I assembled a function to convert the ORM entity directly to a struct, and recursively convert any nested entities from a relationship. The resulting function is in its full glory below:

view plain print about
1<cffunction name="EntityToStruct" description="Converts an ORM entity to a struct">
2    <cfargument name="object" hint="ORM entity to convert to struct">
3    
4    <!--- Var Scoping --->
5    <cfscript>
6        var struct = {};
7        var property = '';
8        var metadata = GetMetadata(arguments.object);
9        var nestedMetadata = '';
10        var value = '';
11        var tempValue = [];
12        var item = '';
13        var tempItem = '';
14    
</cfscript>
15    <!--- End Var Scoping --->
16    
17    <cfloop array="#metadata.properties#" index="property">
18        
19        <!--- Getters are predicatable, the word 'get' plus the property name, however they must be called with CFInvoke --->        
20        <cfset getter = "get" & property.Name>
21        <cfinvoke method="#getter#" component="#arguments.object#" returnvariable="value">
22        
23        <!--- Optional properties are undefined --->
24        <cfif isNull(value)>
25            <cfset value = ''>
26        </cfif>
27        
28        <!--- Handle any nested components --->    
29        <cfif isArray(value)>
30            <cfset tempValue = []>
31            <cfloop index="item" from="1" to="#arraylen(value)#">
32                <cfset nestedMetadata = GetMetadata(value[item])>
33                <cfif StructKeyExists(nestedMetadata,"type")>
34                    <cfif nestedMetadata.type eq "component">
35                        <cfset tempItem = value[item]>
36                        <cfset arrayAppend(tempValue, EntityToStruct(tempItem))>
37                    </cfif>
38                </cfif>
39            </cfloop>
40            <cfset value = tempValue>
41        <cfelse>
42            <cfset nestedMetadata = GetMetadata(value)>
43            <cfif StructKeyExists(nestedMetadata,"type")>
44                <cfif nestedMetadata.type eq "component">
45                    <cfset value = EntityToStruct(value)>
46                </cfif>
47            </cfif>            
48        </cfif>
49        
50        <!--- Add to the new struct --->
51        <cfset StructInsert(struct, property.Name, value)>
52
53    </cfloop>
54    
55    <cfreturn struct>
56</cffunction>

This function then puts all the data from an entity into a struct, which can safely be serialized to JSON with all of its mapped entities serialized as well, and completely avoid this bug. And it could potentially have uses elsewhere, but I haven't discovered those yet.

'Till next time, Mike