As you probably know, CFML doesn't have any concept of nulls within it's own type system. It has some facilities for dealing with nulls due to interfaces with other type systems (e.g., from Java or JavaScript). At least on ColdFusion, it's not perfect. Consider this code:
s = { 'javaCast' = javaCast('null', 0), 'json' = deserializeJson('null') }; structKeyExists(s, 'javaCast'); // false structKeyExists(s, 'json'); // true structKeyExists(s, 'missing'); // false isNull(s.javaCast); // true isNull(s.json); // false isNull(s.missing); // true
As you can see, there is no difference between null and missing. However, the null from the JSON gets deserialized not as a null, but as "null" (i.e., the string containing the letters 'n', 'u', 'l', and 'l'). Wait, what?
I don't think there should be any confusion about how contrived this example is. However, the issue manifests itself any time you're deserializing JSON packets with ColdFusion. So watch out.
Treating a null value of a struct as if it were missing is dangerous, I think, and should probably be reported as a bug. I have a hard time imagining how that behavior could be right.
With the serialization, however, I'm not sure that's a bug. Or even all that unusual or unexpected. I did a quick test in C#:
StringBuilder sb = new StringBuilder();
new JavaScriptSerializer().Serialize(null, sb);
At the end of this, sb will have the string value of "null", and sb.ToString() == null will evaluate as false. Which makes sense, really. JSON serializes an object and returns a string value. If a null value didn't have some kind of string representation, I think it would break the JSON formatting.
Another interesting thing about NULL struct keys is that they are still available during struct iteration (ie. collection loop). I ran into that once when looping over non-required CFArgument keys — they were in the loop but caused a problem when I tried to reference them.
Matt, I disagree. Historically, a null value was "missing" in CFML parlance, and rightfully so. In CF9, Adobe added isNull to differentiate the two cases (because it's needed for using Hibernate), but there's a LOT of code sitting around which equates a null value to missing.
Your test about JSON serialization is flawed. Obviously the serialized version of a null will be the string "null". That's stipulated by the JSON spec. And that's what your test was checking. For purposes of comparison, I've rewritten it here:
I don't know C#, but your test needs to be something like this (forgive the erroneous types and syntax):
You have to deserialize to get back to the environment – the serialized format is always strings.
Ben, that's exactly the problem which manifested this post. It was in another developer's app, inverted, and was a normal struct (not the arguments scope), but that's the gist of it. CF rather screwed up in making null behave both ways. It was pretty much masked until recently, but the simple fact is that if you run on the JRE you have nulls, and any JRE language needs to acknowledge that fact.
So is there a way to test the difference between {"name" : null} and {"name" : "null"} after deserialization?
D,
Assuming you deserialize into '
map
', then#structKeyExists(map, 'name')#
will return 'false
' for the first and 'true
' for the second.That didn't work (using CF9; is it different in CF10?). The following returned
YES
for both tests, and keys in both contained the string value"null"
:I'm sorry, I assumed you were referring to CF structs, not JSON packets. The impetus for my original post was the inability to differentiate between
null
s in JSON, exactly as your packets illustrate.