Monday 14 October 2013

CFCamp: new (to me) function in Railo/CFML: serialize()

G'day:
Gert gave typically bloody interesting keynote presentation this morning on what's been happening in Railo over the last year or so, where it's at now, and where it's headed for its next version. I might write up that part of it at some stage, but that sort of thing takes a while to write up and requires more concentration than I feel like dedicating at the moment, so I'm just gonna focus on some of the code stuff he showed us.

First thing... take note, Rakshith: Gert showed us code. Not smoke and mirrors and marketing spiel, but code.

There was a bunch of stuff that was new to me, and I'll write that up separately, but here's a quick examination of Railo's serialize() function. The docs are here: serialize(), but there's not really any useful information there. What serialize() does is to... serialise data, which can later be evaluated (using, yes, evaluate()) back to CFML data again. Here's an example using an array:

rainbow                    = ["Whero","Karaka","Kowhai","Kakariki","Kikorangi","Tawatawa","Mawhero"];
serialisedViaFunction    = serialize(rainbow);
deserialised            = evaluate(serialisedViaFunction);

writeDump([rainbow,serialisedViaFunction,deserialised]);  

Here we get the output:


Array
1
Array
1
stringWhero
2
stringKaraka
3
stringKowhai
4
stringKakariki
5
stringKikorangi
6
stringTawatawa
7
stringMawhero
2
string['Whero','Karaka','Kowhai','Kakariki','Kikorangi','Tawatawa','Mawhero']
3
Array
1
stringWhero
2
stringKaraka
3
stringKowhai
4
stringKakariki
5
stringKikorangi
6
stringTawatawa
7
stringMawhero

This just demonstrates that it's a reverseable process. In this example the array is serialised as JSON, which makes perfect sense:

['Whero','Karaka','Kowhai','Kakariki','Kikorangi','Tawatawa','Mawhero']

So what's the difference between serialize() and serializeJson()? In this case not a lot. But this is because JSON is a sensible way to serialise an array of simple values. The thing is, though, that serialize() is not a mechanism to translate to and from JSON, it's just for serialising CFML data: JSON might be the best interchange format, but equally it might not be (we'll get to that).

Here's an example using a query. A query is a good example that demonstrates the difference between serialising to/from JSON, and just serialising specifically targeted at serialising CFML data.

numbers = queryNew("id,ma,en", "integer,varchar,varchar", [
    [1, "tahi", "one"],
    [2, "rua", "two"],
    [3, "toru", "three"],
    [4, "wha", "four"]
]);
serialised= serialize(numbers);
deserialised = evaluate(serialised);
writeDump([numbers,serialised,deserialised]);  

Array
1
Query
Execution Time (ms):0 
Recordcount:4 
Cached:No 
Lazy:No 

idmaen
11tahione
22ruatwo
33toruthree
44whafour
2
stringquery('id':[1,2,3,4],'ma':['tahi','rua','toru','wha'],'en':['one','two','three','four'])
3
Query
Execution Time (ms):0 
Recordcount:4 
Cached:No 
Lazy:No 

idmaen
11tahione
22ruatwo
33toruthree
44whafour

Here... Railo's not using JSON, it's using a different serialisation approach:

query('id':[1,2,3,4],'ma':['tahi','rua','toru','wha'],'en':['one','two','three','four'])

This is because JSON doesn't know what a CFML "query" data type is, so the best it can do is serialise as an array of structs. Adobe have bodged (de)serializeJson() so they create JSON of a specific schema which - if instructed with a special parameter on serializeJson() and deserializeJson() - CF will correctly deserialise the JSON as a query object.

Below is code not using the special setting to serialise a query to JSON on ColdFusion:

numbers = queryNew("id,ma,en", "integer,varchar,varchar", [
    [1, "tahi", "one"],
    [2, "rua", "two"],
    [3, "toru", "three"],
    [4, "wha", "four"]
]);
serialised= serializeJson(numbers);
deserialised = deserializeJson(serialised);
writeDump([numbers,serialised,deserialised]);  

And we see the data is not deserialised properly, as the sense of the data being a query is lost:

array
1
query
ENIDMA
1one1tahi
2two2rua
3three3toru
4four4wha
2{
"COLUMNS":["ID","MA","EN"],
"DATA":[
[1,"tahi","one"],
[2,"rua","two"],
[3,"toru","three"],
[4,"wha","four"]
]}
3
struct
COLUMNS
array
1ID
2MA
3EN
DATA
array
1
array
11
2tahi
3one
2
array
12
2rua
3two
3
array
13
2toru
3three
4
array
14
2wha
3four

We've ended up with an array containing a struct of schema info and data which is an array of column values. If we use the magic parameters, we can get a query back:

deserialised = deserializeJson(serialised, false);

The false here means - as per the deserializeJson() docs -  "Determine if the JSON string contains representations of ColdFusion queries, and if so, convert them to queries.". It's false because the argument works like this:

strictMapping
A Boolean value that specifies whether to convert the JSON strictly
Which just means "if it was a query, make it back into a query, otherwise just deserialise it as per the normal JSON rules". It's not intuitive.

And here's the thing here with serialize(): it's not trying to shoe-horn CFML data into a serialisation scheme designed for another language, it simply does what it says on the wrapper: serialises CFML data.

One interesting thing here... if one looks at the serialisation of the query, we spot that Railo has a function query(), which works thus:

numbers = query(id=[1,2,3,4], ma=['tahi','rua','toru','wha'], en=['one','two','three','four']);

That query() function takes named arguments which are column names, the value of which is an array of values for that column. This is a nice alternative to queryNew().

serialize() can also serialise CFML objects:

// Name.cfc
component accessors=true {
    property firstName;
    property lastName;
}

// object.cfm
z = new Name();
z.setFirstName("Zachary");
z.setLastName("Cameron Lynch");

serialised= serialize(z);
deserialised = evaluate(serialised);
writeDump([z,serialised,deserialised]); 

This yields:

Array
1
Component (Name) 
Only the functions and data members that are accessible from your location are displayed

Properties
firstname
stringZachary
lastname
stringCameron Lynch
public
2
stringevaluateComponent(
    'shared.git.blogExamples.railo.serialize.Name',
    'fb6958d780d7ef899cd9a25da21987b6',
    struct(),
    struct('lastname':'Cameron Lynch','firstname':'Zachary')
)
3
Component (shared.git.blogExamples.railo.serialize.Name) 
Only the functions and data members that are accessible from your location are displayed

Properties
firstname
stringZachary
lastname
stringCameron Lynch
public

This leverages another "new to me" function: evaluateComponent().

The sceptic in my decided to test a more complicated situation as well:

// Person.cfc
component  accessors=true {
    property Name name;
    property date dob;
}

// person.cfm
z = new Person();
name = new Name();
name.setFirstName("Zachary");
name.setLastName("Cameron Lynch");

z.setName(name);
z.setDob(createDate(2011,3,24));

serialised= serialize(z);
writeOutput("serialised: #serialised#<br><br>");
deserialised = evaluate(serialised);

writeOutput("First Name: #z.getName().getFirstName()#<br>");
writeOutput("Last Name: #z.getName().getLastName()#<br>");
writeOutput("DOB: #z.getDob()#<br>");

And this works fine:

serialised: evaluateComponent(
    'shared.git.blogExamples.railo.serialize.Person',
    '90b7182a9413138dccaf79112b2d39f3',
    struct(),
    struct(
        'dob'    : createDateTime(2011,3,24,0,0,0,0,"Europe/Berlin"),
        'name'    : evaluateComponent(
            'shared.git.blogExamples.railo.serialize.Name',
            'fb6958d780d7ef899cd9a25da21987b6',
            struct(),
            struct(
                'lastname'    : 'Cameron Lynch',
                'firstname' :'Zachary'
            )
        )
    )
)

First Name    : Zachary
Last Name    : Cameron Lynch
DOB            : {ts '2011-03-24 00:00:00'}

I wondered whether I could put a _toString() method in my CFC and whether serialize() might use that (for more about _toString(), read my earlier article "Cool Railo thing I learned at the London Railo Group meeting last night"), but that did't work. Fair enough: converting to a string is not necessarily the same as serialising, per se. I guessed there might be a _serialize() method one could specify instead, but that didn't work either. Not to worry.

Lastly, one can even serialise Java objects. With, seemingly, a caveat. Here's my test code:

// Name.java
public class Name implements java.io.Serializable {
    public String firstName;
    public String lastName;
}

// Java.cfm
z = createObject("java", "Name");
z.firstName = "Zachary";
z.lastName = "Cameron Lynch";
writeDump(z);
    
try{
    serialised= serialize(z);
    writeDump(serialised);

    try{
        deserialised = evaluate(serialised);
        writeDump(deserialised);  
    }
    catch (any e){
        writeOutput("#e.type#: #e.message# #e.detail#<br>")
    }
}
catch (any e){
    writeOutput("#e.type#: #e.message# #e.detail#<br>")
}

This only half works:

Name
className
fields
namepatternvalue
firstNamepublic java.lang.String Name.firstNameZachary
lastNamepublic java.lang.String Name.lastNameCameron Lynch
methods
returninterfaceexceptions

Methods inherited from java.lang.Object
equals, toString, hashCode, getClass, notify, notifyAll, wait, wait, wait
stringevaluateJava('rO0ABXNyAAROYW1lK9Hyb97nlp8CAAJMAAlmaXJzdE5hbWV0ABJMamF2YS9sYW5nL1N0cmluZztMAAhsYXN0TmFtZXEAfgABeHB0AAdaYWNoYXJ5dAANQ2FtZXJvbiBMeW5jaA==')
java.lang.ClassNotFoundException: Name
The serialisation seems to work fine, and the deserialisation can detect it's a Name object, but it fails after that. Now all I did with this class I created was compile it and chuck it in the [railo]\webapps\www\WEB-INF\railo\classes directory, and restart Railo. That was enough for Railo to find the class to instantiate it, so I'd've thought that's all that's necessary for the deserialisation to work? Maybe not.

Anyway, I'll collar Gert at the bar later on and ask about serialising Java objects, and maybe ask him about _serialize(), too, to provide custom serialisers for CFCs.

I've got more to write up, but I'm listening to Birgit present on Mango Blog, and I want to pay more attention...

Righto.

--
Adam