MongoDB BSON Structs Are Case-Sensitive In Lucee CFML 5.3.6.61
Yesterday, I started to consider what a data-access component for MongoDB might look like in Lucee CFML. This thought process was initiated by the fact that I ran into a surprising key-case-sensitivity issue with the BSON documents returned by the MongoDB Java Driver. This isn't the first time that I've tripped over unexpected BSON document behaviors (see my post on incrementing BSON key-values); but, since I do enjoy documenting all of my problems, I figured I'd share this one as well in Lucee CFML 5.3.6.61.
ColdFusion / CFML is a case-insensitive language (for the most part). Personally, I've never really felt this was a "feature"; but, it is what it is. And, part of that case-insensitive behavior is the ability to look up Struct keys with arbitrary key-casing.
When querying a MongoDB database using the Java Driver in ColdFusion, results are returned to the ColdFusion context as BSON objects. BSON implements the Map
interface, the same as any ColdFusion Struct. But, BSON keys are case sensitive. This can cause issues if you are working with legacy code that has inconsistent key-casing (as I often am).
To see this in action, we don't even need to query MongoDB - we can just instantiate a BSON document and try to access the keys:
<cfscript>
nativeStruct = [ "foo" : "bar" ];
bsonStruct = toBson( nativeStruct );
echo( "<b>Testing Native ColdFusion Struct</b> <br />" );
echo( "<code>foo</code> exists: #nativeStruct.keyExists( 'foo' )# <br />" );
echo( "<code>FOO</code> exists: #nativeStruct.keyExists( 'FOO' )# <br />" );
echo( "<br />" );
echo( "<b>Testing BSON Struct</b> <br />" );
echo( "<code>foo</code> exists: #bsonStruct.keyExists( 'foo' )# <br />" );
echo( "<code>FOO</code> exists: #bsonStruct.keyExists( 'FOO' )# <br />" );
echo( "<br />" );
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
/**
* I perform a SHALLOW CONVERSION of the given Struct to a BSON document.
*
* CAUTION: For OPERATION documents (such as those defining a search filter), the
* passed-in Struct should be a LINKED / ORDERED struct so that the keys will be
* output in the same order in which they were defined. Depending on the operation in
* question, this may be a hard requirement of how the MongoDB API works.
*
* @value I am the struct to convert to a BSON document.
*/
public any function toBson( required struct value ) {
return( loadClass( "org.bson.Document" ).init( value ) );
}
/**
* I load the given Java class.
*
* @className I am the Java class being loaded.
*/
public any function loadClass( required string className ) {
return( createObject( "java", className ) );
}
</cfscript>
As you can see, I am creating a linked / ordered struct, which I often do when interacting with MongoDB. Then, I perform a shallow-conversion of that native ColdFusion struct to a BSON document. I then try to look up correct and incorrect key-cased keys in both objects. And, here's what we get:
As you can see, after I define both a Struct and a BSON document with key, foo
, trying to access the key, FOO
(all uppercase), works fine, as expected, on the native ColdFusion struct; but, fails on the BSON document.
Again, if you just use consistent key-casing in your ColdFusion code, this would never even be an issue. But, when working with legacy code, this isn't always the case (no pun intended). As such, it can be important to know that the "Struct behavior" of a BSON document coming out of MongoDB diverges from the behavior of native ColdFusion Structs in Lucee CFML 5.3.6.61.
Want to use code from this post? Check out the license.
Reader Comments
Wouldn't DeserializeJson(SerializeJson(doc)) normalize it back to normal CFML?
@Zachary,
Yeah, I think that would also do the trick. I was actually contemplating creating a
fromBson()
function that would use that exact idea:I was also considering use an
.append()
approach as well, which would perform a shallow conversion back to the native Struct:Ultimately, what I think I might go with is just explicitly creating Structs with explicit keys, as I look at in my previous post:
www.bennadel.com/blog/3914-considering-data-workflows-within-a-mongodb-data-access-layer-in-lucee-cfml-5-3-6-61.htm
This way, I can see exactly what comes out of the MongoDB database. I feel like you get a better sense of what the code is actually doing.