Tuesday, 6 August 2013

ColdFusion & JSON: yet another bug

G'day:
This might actually be a manifestation of the same issue I wrote about a while back: "Right... so JSON is being a pain in the arse again". And I didn't spot this one myself - I've given up on using JSON in ColdFusion as it's just too unstable - but read about it on a thread on CF-TALK (it's not in the archive yet, so I cannae link to it).

Anyway, this time the issue is with ColdFusion and "strings that look like numbers" again, but a different slant on it. Consider this code:

number = .0006;
struct = {number=number};
json = serializeJSON(struct);
writeDump([
    {number=number},
    {"struct.number"=struct.number},
    {struct=struct},
    {json=json}
]);

On ColdFusion (9, and apparently 10 too, going from the thread I can't link you to...), the output is this:

array
1
struct
NUMBER.0006
2
struct
struct.number.0006
3
struct
STRUCT
struct
NUMBER.0006
4
struct
JSON{"NUMBER":6.0E-4}

Nice. Thanks for that, ColdFusion. Just in case yer really going "wah??" there, that's scientific notation for 0.0006. So, ColdFusion... which part of "turn this into JSON" did you interpret as "turn this into JSON and any numbers with more then three decimal place? Scientific notation please". Just to demonstrate that this is not "just one of those things", here's the output in Railo:


Array
1
Struct
NUMBER
number0.0006
2
Struct
struct.number
number0.0006
3
Struct
STRUCT
Struct
NUMBER
number0.0006
4
Struct
JSON
string{"NUMBER":0.0006}

All good.

I extended this code to try to work out WTF is going on:
number = .0006;
forcedNumber = number * 1;
forcedStringJava = number.toString();
forcedStringCfml = toString(number);

double = createObject("java", "java.lang.Double").init(0.0006);
float = createObject("java", "java.lang.Float").init(0.0006);

string = ".0006";

struct = {number=number,forcedNumber=forcedNumber,forcedStringJava=forcedStringJava,forcedStringCfml=forcedStringCfml,double=double,float=float,string=string};
json = serializeJSON(struct);
writeDump([
    {"struct.number"=struct.number},
    {"struct.forcedNumber"=struct.forcedNumber},
    {"struct.forcedStringJava"=struct.forcedStringJava},
    {"struct.forcedStringCfml"=struct.forcedStringCfml},
    {"struct.double"=struct.double},
    {"struct.float"=struct.float},
    {"struct.string"=struct.string},
    {struct=struct},
    {json=json}
]);

And the output didn't really have me any the wiser:

array
1
struct
struct.number.0006
2
struct
struct.forcedNumber0.0006
3
struct
struct.forcedStringJava.0006
4
struct
struct.forcedStringCfml.0006
5
struct
struct.double0.0006
6
struct
struct.float6.0E-4
7
struct
struct.string.0006
8
struct
STRUCT
struct
DOUBLE0.0006
FLOAT6.0E-4
FORCEDNUMBER0.0006
FORCEDSTRINGCFML.0006
FORCEDSTRINGJAVA.0006
NUMBER.0006
STRING.0006
9
struct
JSON{
"DOUBLE":6.0E-4,
"FORCEDSTRINGJAVA":6.0E-4,
"FLOAT":6.0E-4,
"FORCEDSTRINGCFML":6.0E-4,
"FORCEDNUMBER":6.0E-4,
"STRING":6.0E-4,
"NUMBER":6.0E-4
}

Railo had an interesting quirk:

Array
1
Struct
NUMBER
number0.0006
2
Struct
struct.number
number0.0006
3
Struct
struct.forcedNumber
number0.0006
4
Struct
struct.forcedStringJava
string6.0E-4
5
Struct
struct.forcedStringCfml
string0.0006
6
Struct
struct.double
number0.0006
7
Struct
struct.float
number0.000600000028
8
Struct
struct.string
string.0006
9
Struct
STRING
string.0006
10
Struct
STRUCT
Struct
DOUBLE
number0.0006
FLOAT
number0.000600000028
FORCEDNUMBER
number0.0006
FORCEDSTRINGCFML
string0.0006
FORCEDSTRINGJAVA
string6.0E-4
NUMBER
number0.0006
STRING
string.0006
11
Struct
JSON
string{
"NUMBER":0.0006,
"FORCEDSTRINGCFML":"0.0006",
"STRING":".0006",
"FORCEDNUMBER":0.0006,
"FLOAT":0.000600000028,
"FORCEDSTRINGJAVA":"6.0E-4",
"DOUBLE":0.0006
}

(Highlighting things in a Railo dump is a bit of a challenge ;-)

So that's some floating point inaccuracy at work which Railo lets us know about (good) but ColdFusion hides from us (bad).

Anyway, casting from a float to a string (outputting it necessitates it being cast to a string) results in the scientific notation, but this should be irrelevant as numerics in ColdFusion are all doubles, to the best of my knowledge? I presume this is a cock-up under the hood of their JSON serialiser. Someone on Twitter suggested the other day that Adobe would be better off using an external library rather than rolling their own - given they seem incapable of doing the job properly - might be a good idea. I agree. I could not agree more.

One interesting thing in all this. Check this out:

noLeadingZero = .0006;
withLeadingZero = 0.0006;
struct = {noLeadingZero=noLeadingZero, withLeadingZero=withLeadingZero};
json = serializeJSON(struct);
writeDump([
    {struct=struct},
    {json=json}
]);

array
1
struct
STRUCT
struct
NOLEADINGZERO.0006
WITHLEADINGZERO0.0006
2
struct
JSON{"NOLEADINGZERO":6.0E-4,"WITHLEADINGZERO":0.0006}

Right. So having the leading zero results in ColdFusion treating the value differently. WTF, Adobe?

--
Adam