Sunday, 23 February 2014

ColdFusion 11: preserveCaseForStructKey

G'day:
There's a new feature in ColdFusion 11:

Case preservation of struct keys

Currently, the cases for struct keys are not preserved in ColdFusion. The struct keys get converted to upper case automatically.
[...]
To enable case preservation of struct keys at the application level, modify the application.cfc file by setting:
this.serialization.preservecaseforstructkey = true

On a whim, I decided to check how well this had been implemented...

The first problem with this is the setting name: this.serialization.preservecaseforstructkey. This is not to do with serialisation... it's simply to do with preserving key case in structs. That the chief use of this is going to be when an object is serialised is neither really here nor there. So it needs a new name. I suggest just ditching the reference to serialisation. Or stick it in a substruct called "codeSettings" or something.

Additionally, I can't help but think this should be the default behaviour, and if - for some weirdo reason - one wanted it off, then one should use the config setting to do so. In the normal course of events, why would one simply not just want to preserve variable name case?

Secondly... wouldn't this be more a compiler setting than an application setting?

Thirdly... does it work? Here's some code that tests some options:

server.lowercase = true;
server.UPPERCASE = true;
server.mixedCase = true;
json = serializeJson(server);
cf = deserializeJson(json);
writeDump(var=[{original=server},{json=json},{deserialiised=cf}], label="server scope");
writeOutput("<hr>");

application.lowercase = true;
application.UPPERCASE = true;
application.mixedCase = true;
json = serializeJson(application);
cf = deserializeJson(json);
writeDump(var=[{original=application},{json=json},{deserialiised=cf}], label="application scope");
writeOutput("<hr>");

session.lowercase = true;
session.UPPERCASE = true;
session.mixedCase = true;
json = serializeJson(session);
cf = deserializeJson(json);
writeDump(var=[{original=session},{json=json},{deserialiised=cf}], label="session scope");
writeOutput("<hr>");

request.lowercase = true;
request.UPPERCASE = true;
request.mixedCase = true;
json = serializeJson(request);
cf = deserializeJson(json);
writeDump(var=[{original=request},{json=json},{deserialiised=cf}], label="request scope");
writeOutput("<hr>");

structClear(variables);
variables.lowercase = true;
variables.UPPERCASE = true;
variables.mixedCase = true;
json = serializeJson(variables);
cf = deserializeJson(json);
writeDump(var=[{original=variables},{json=json},{deserialiised=cf}], label="variables scope");
writeOutput("<hr>");

dotNotation = {};
dotNotation.lowercase = true;
dotNotation.UPPERCASE = true;
dotNotation.mixedCase = true;
json = serializeJson(dotNotation);
cf = deserializeJson(json);
writeDump(var=[{original=dotNotation},{json=json},{deserialiised=cf}], label="struct with dot notation");
writeOutput("<hr>");

literalNotation = {
    lowercase = true,
    UPPERCASE = true,
    mixedCase = true
};
json = serializeJson(literalNotation);
cf = deserializeJson(json);
writeDump(var=[{original=literalNotation},{json=json},{deserialiised=cf}], label="struct with literal notation");
writeOutput("<hr>");

viaStructInsert = structNew();
structInsert(viaStructInsert, "lowercase", true);
structInsert(viaStructInsert, "UPPERCASE", true);
structInsert(viaStructInsert, "mixedCase", true);
json = serializeJson(viaStructInsert);
cf = deserializeJson(json);
writeDump(var=[{original=viaStructInsert},{json=json},{deserialiised=cf}], label="struct via structInsert()");
writeOutput("<hr>");

Here I put some case-significant keys into various scopes: server, application, session, request, variables; as well as into structs using various syntax options: dot notation, struct-literal notation, and using structInsert().

And the results:

server scope - array
1
server scope - struct
original
server scope - struct
UPPERCASEtrue
coldfusion
server scope - struct
InstallKitNative Windows
appserverTomcat
expiration{ts '2014-05-15 09:40:59'}
productlevelEvaluation
productnameColdFusion Server
productversion11,0,0,288464
rootdirC:\apps\adobe\ColdFusion\11beta\gettingstarted\cfusion
supportedlocales
lowercasetrue
mixedCasetrue
os
server scope - struct
additionalinformation[empty string]
archamd64
buildnumber[empty string]
nameWindows 8
version6.2
2
server scope - struct
json{
"mixedCase":true,
"lowercase":true,
"UPPERCASE":true
}
3
server scope - struct
deserialiised
server scope - struct
UPPERCASEYES
coldfusion
server scope - struct
InstallKitNative Windows
appserverTomcat
expirationMay, 15 2014 09:40:59
productlevelEvaluation
productnameColdFusion Server
productversion11,0,0,288464
rootdirC:\apps\adobe\ColdFusion\11beta\gettingstarted\cfusion
supportedlocales
lowercaseYES
mixedCaseYES
os
server scope - struct
additionalinformation[empty string]
archamd64
buildnumber[empty string]
nameWindows 8
version6.2

application scope - array
1
application scope - struct
original
application scope - struct
applicationnametestKeyCase01
lowercasetrue
mixedcasetrue
uppercasetrue
2
application scope - struct
json{"mixedcase":true,"uppercase":true,"lowercase":true,"applicationname":"testKeyCase01"}
3
application scope - struct
deserialiised
application scope - struct
applicationnametestKeyCase01
lowercaseYES
mixedcaseYES
uppercaseYES

session scope - array
1
session scope - struct
original
session scope - struct
cfid118
cftokend6d97e96918f566f-9A809602-EB3B-ADF4-7353897DF8912428
lowercasetrue
mixedcasetrue
sessionidTESTKEYCASE01_118_d6d97e96918f566f-9A809602-EB3B-ADF4-7353897DF8912428
uppercasetrue
urltokenCFID=118&CFTOKEN=d6d97e96918f566f-9A809602-EB3B-ADF4-7353897DF8912428
2
session scope - struct
json{"mixedcase":true,"uppercase":true,"urltoken":"CFID=118&CFTOKEN=d6d97e96918f566f-9A809602-EB3B-ADF4-7353897DF8912428","cftoken":"d6d97e96918f566f-9A809602-EB3B-ADF4-7353897DF8912428","lowercase":true,"cfid":118,"sessionid":"TESTKEYCASE01_118_d6d97e96918f566f-9A809602-EB3B-ADF4-7353897DF8912428"}
3
session scope - struct
deserialiised
session scope - struct
cfid118
cftokend6d97e96918f566f-9A809602-EB3B-ADF4-7353897DF8912428
lowercaseYES
mixedcaseYES
sessionidTESTKEYCASE01_118_d6d97e96918f566f-9A809602-EB3B-ADF4-7353897DF8912428
uppercaseYES
urltokenCFID=118&CFTOKEN=d6d97e96918f566f-9A809602-EB3B-ADF4-7353897DF8912428

request scope - array
1
request scope - struct
original
request scope - struct
lowercasetrue
mixedcasetrue
uppercasetrue
2
request scope - struct
json{"uppercase":true,"lowercase":true,"mixedcase":true}
3
request scope - struct
deserialiised
request scope - struct
lowercaseYES
mixedcaseYES
uppercaseYES

variables scope - array
1
variables scope - struct
original
variables scope - struct
CF
variables scope - struct
UPPERCASEYES
lowercaseYES
mixedCaseYES
JSON{"lowercase":true,"UPPERCASE":true,"mixedCase":true}
UPPERCASEtrue
lowercasetrue
mixedCasetrue
2
variables scope - struct
json{"lowercase":true,"UPPERCASE":true,"mixedCase":true}
3
variables scope - struct
deserialiised
variables scope - struct
UPPERCASEYES
lowercaseYES
mixedCaseYES

struct with dot notation - array
1
struct with dot notation - struct
original
struct with dot notation - struct
UPPERCASEtrue
lowercasetrue
mixedCasetrue
2
struct with dot notation - struct
json{"mixedCase":true,"UPPERCASE":true,"lowercase":true}
3
struct with dot notation - struct
deserialiised
struct with dot notation - struct
UPPERCASEYES
lowercaseYES
mixedCaseYES

struct with literal notation - array
1
struct with literal notation - struct
original
struct with literal notation - struct
UPPERCASEtrue
lowercasetrue
mixedCasetrue
2
struct with literal notation - struct
json{"mixedCase":true,"UPPERCASE":true,"lowercase":true}
3
struct with literal notation - struct
deserialiised
struct with literal notation - struct
UPPERCASEYES
lowercaseYES
mixedCaseYES

struct via structInsert() - array
1
struct via structInsert() - struct
original
struct via structInsert() - struct
UPPERCASEtrue
lowercasetrue
mixedCasetrue
2
struct via structInsert() - struct
json{"mixedCase":true,"UPPERCASE":true,"lowercase":true}
3
struct via structInsert() - struct
deserialiised
struct via structInsert() - struct
UPPERCASEYES
lowercaseYES
mixedCaseYES


The results aren't bad, but aren't perfect either. With basic structs, everything works OK. However with some of the scopes, it's not working properly: the application, session and request scopes do not preserve key case still.

To be honest, this is a slightly contrived test, but Adobe should just do the job properly and thoroughly.

Incidentally: Railo already has this functionality, and it works for everything except the request scope:


request scope
Array
1
Struct
original
request
lowercase
booleantrue
mixedcase
booleantrue
uppercase
booleantrue
2
Struct
json
string{"uppercase":true,"lowercase":true,"mixedcase":true}
3
Struct
deserialised
Struct
lowercase
booleantrue
mixedcase
booleantrue
uppercase
booleantrue


So... it's good that they've decided to do this - and about time! - but there's still a bit of work to do.

Here's an issue though. What Adobe is trying to deal with here is that in other systems, object names can be case-sensitive. Most significantly, JavaScript is. I think Adobe have dealt with half the issue here, but have not dealt with the whole issue. Consider this code:

// inbound.cfm
json = '{"a":"a","A":"A"}';
cfml = deserializeJSON(json);
writeDump([{json=json},{cfml=cfml}]);

Here the key names are case-sensitive... and... other than case, the key names are the same. CFML still does not deal with this:

array
1
struct
json{"a":"a","A":"A"}
2
struct
cfml
struct
aA

This is predictable, and is different from "case preservation". But isn't this still part of what Adobe are trying to deal with here?

Also, there is data loss there: part of the inbound data simply vanishes without any notification from ColdFusion. I think this is probably not a good thing.

That said, it's also an edge-case, so I'm gonna file that one under "food for thought", not "it's an issue".

I can't help but think though that if Adobe are gong down this route of changing CF to pay attention to casing issues... maybe it should do a complete job of it? Obviously as a toggle-able setting though.

Oh... and for the sake of completeness I tested with WDDX as well, and it worked fine.

Bottom line:
  • rename the config setting (3712134);
  • fix the application, session and request scopes to do the job properly (3712135).
--
Adam