There was a Twitter conversation today where Ben Nadel mentioned that one of the continuing issues with ColdFusion was bad JSON support.  I assume by that, he's referring to CF's tendency to change the datatype of variables while serializing them. I mentioned that Lucee Server has much better support for this and I'd like to show very quickly why this is.

PLEASE NOTE: Adobe has made some significant changes in Release 2018 and handles internal data types much more similar to Lucee now which great news for Adobe JSON users!

String All The Things!

It all boils down to this:  In Adobe ColdFusion, every single simple variable is stored as a java.lang.String.  Numbers? Stored as a string!  Booleans? Stored as a string!  Dates?  Well, actually Adobe does store these an actual date object if you create it with createDate(), so that's a win.  Of course, JSON has no native date datatype so it doesn't help us here.  The point is, booleans and numbers all lose information about what they used to be when Adobe CF stores them in memory.  That means 5 and "5" are the same thing.  Same with true and "true".  Even "yes" and true serialize are the same!! This causes problems when you need to output those values into a format where type matters (like JSON).  

What happens is Adobe CF has to "guess" what the datatypes are since it threw away the information of what they were when it created them.  This leads to problems where strings like "yes" turn into true and strings like "123" turn into a number.  They may look similar, but if the JSON is being consumed by a statically typed language, it can cause errors.  

Keep It Native

So how does Lucee store a number?  As a java.lang.Double.  How does Lucee store a boolean?  As a java.lang.Boolean!  Internally, Lucee preserves the original data type so when it comes time to build JSON out of those values, Lucee doesn't do any guessing at all!  it simply outputs them using their retained native data type.  Lucee will still do run time casting of data types to another data type based on the context of the operation.  For example, appending the text "foo" onto the number 5.  But up until Lucee appends "foo", it's still recognized as a number. (Well, actually Strings are immutable so the numeric 5 never goes away, you just create a new variable, but I digress)

Example

So, let's look at how this works in a simple example that hopefully will showcase what's happening between the engines.  This code will create a struct with a few different data types and then output the internal java data type for each key.  Finally, it will serialize our struct into a JSON string.

data = {
    boolean : true,
    stringTrue : 'true',
    stringYes : 'yes',
    number : 5,
    stringFive : '5'
};

data.each( function( key, value ) {
    writeOutput( key & ' - ' & value.getClass().getName() & '<br>' );
} );

writeOutput( '<br>' );
writeDump( serializeJSON( data ) );

Here's a Trycf.com link to run as Adobe CF 2016

https://trycf.com/gist/0be63fbe4668da489454b7e4a9a65609/acf2016?theme=monokai

And here's a Trycf.com link for Lucee 5:

 https://trycf.com/gist/33a8948c5b2aa43f673c31545d2fd886/lucee5?theme=monokai

Adobe CF 2016 Output

I added lines breaks to the JSON string for readability.

NUMBER - java.lang.String   // <--DATA LOSS
BOOLEAN - java.lang.String   // <--DATA LOSS
STRINGYES - java.lang.String
STRINGTRUE - java.lang.String
STRINGFIVE - java.lang.String

{
    "NUMBER":5,   // <--Lucky guess
    "BOOLEAN":true,   // <--Lucky guess
    "STRINGYES":true,   // <--Incorrect guessing
    "STRINGTRUE":true,   // <--Incorrect guessing
    "STRINGFIVE":5   // <--Incorrect guessing
}

Lucee 5 Output

I added lines breaks to the JSON string for readability.

NUMBER - java.lang.Double  // <-- Original type preserved
BOOLEAN - java.lang.Boolean  // <-- Original type preserved
STRINGTRUE - java.lang.String
STRINGYES - java.lang.String
STRINGFIVE - java.lang.String

{
    "NUMBER":5,  // <-- Correct! No guessing 
    "BOOLEAN":true,  // <-- Correct! No guessing 
    "STRINGTRUE":"true",  // <-- Correct! No guessing 
    "STRINGYES":"yes",  // <-- Correct! No guessing 
    "STRINGFIVE":"5"  // <-- Correct! No guessing 
}

What Does It Mean

So, pretty self explanatory.  You can see that Adobe CF throws away information about your data types in such a way that it is required to later make a guess as to what the data is.  It works in some case, but not in others.  Lucee retains the original information about your numbers and booleans, so later on it doesn't NEED to guess.  It just uses them as they are.  This also provides a performance boost for Lucee because it doesn't need to cast string back to numbers every time you perform a math operation on them, just to cast them back to a string to store them!  Lucee only casts when you want to do an operation with a variable that isn't in line with its native type.  

What Can You Do About It?

If you're an ACF user not much other than switch to Lucee :)  ACF 2016 has introduced some features to allow you to try and help it guess better when serializing JSON.  https://helpx.adobe.com/coldfusion/cfml-reference/coldfusion-functions/functions-s/serializejson.html  Honestly, it's all still a big workaround for not keeping track of the native data types.  CF can fix this, but they would need to make some major changes to how they handle data types internally.  Lucee has been pretty solid on JSON serialization though, and hopefully this post explains why.

PLEASE NOTE: Adobe has made some significant changes in Release 2018 and handles internal data types much more similar to Lucee now which great news for Adobe JSON users!

Here's a the same Gist in Adobe 2018

https://trycf.com/gist/0be63fbe4668da489454b7e4a9a65609/acf2018?theme=monokai