Referencing String Characters Using Array-Notation In ColdFusion 2021
UPDATE 2023-09-02: The logic in this demo is incorrect. Yes, you can reference the characters in a string via their index; however, when you have a for
-loop over a given string value, the loop does not iterate over the characters. The loop simply executes once and the iterator value contains the entire string value.
I had wrongly assumed that since the code was not throwing an error, that it must be working how I thought it was working. But, I failed to actually test that each character was being visited independently.
That said, .each()
and .map()
do seem to iterate over individual characters in Adobe ColdFusion, but not in Lucee CFML.
A few years ago, I mentioned that you can treat Strings like character arrays in Lucee CFML. It turns out, you can do the same thing in Adobe ColdFusion. I am not sure what version this was added in; but, I'm guessing it was ColdFusion 2018 (and the introduction of "array slices"). To help burn this into my brain, I wanted to put together a quick demo.
In the following code, I'm demonstrating three distinct ColdFusion features:
- References letters using index-based access.
- Iterating over letters using a
for-in
loop. - Iterating over letters using member-methods (ex,
.map()
and.each()
).
<cfscript>
value = "hello world";
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
// Iterate over the characters in a string using array notation.
runExample(
() => {
for ( var i = 1 ; i <= value.len() ; i++ ) {
writeOutput( value[ i ] );
}
}
);
// Iterate over the characters in a string as if it were an array.
runExample(
() => {
for ( var letter in value ) {
writeOutput( letter );
}
}
);
// Iterate over the characters in a string using member methods.
runExample(
() => {
var newValue = value.map(
( letter ) => {
return( ucase( letter ) );
}
);
writeOutput( newValue );
}
);
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
/**
* I invoke the given callback and then add a line-break.
*/
public void function runExample( required function callback ) {
callback();
writeOutput( "<br />" );
}
</cfscript>
As you can see, in all three of these examples, I'm treating a String as if it were an Array. And, when we run this ColdFusion 2021 code, we get the following output:
hello world
hello world
HELLO WORLD
Here's something interesting, though: in my last example with ucase()
, I tried to invoke that function as a member method on the character passed into the iteration operator (callback). But, it seems that .ucase()
is not available as a member method in this context. This is likely a bug, but worth being aware of:
<cfscript>
// Iteration using for-in.
for ( letter in "A" ) {
writeDump( letter.getClass().getName() );
writeOutput( "<br />" );
try {
writeOutput( letter.lcase() );
writeOutput( "<br />" );
} catch ( any error ) {
writeOutput( error.message );
writeOutput( "<br />" );
}
}
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
// Iteration as a member method.
"A".each(
( letter ) => {
writeDump( letter.getClass().getName() );
writeOutput( "<br />" );
try {
writeOutput( letter.lcase() );
} catch ( any error ) {
writeOutput( error.message );
}
}
);
</cfscript>
Here, I'm using two forms of iteration - for-in
and member-method - and for each, I'm inspecting the underlying Java class of the given letter. And, when we run this in ColdFusion 2021, we get the following:
java.lang.String
ajava.lang.Character
The lcase method was not found.
As you can see, when using member-method iteration, the letter is actually a different class - java.lang.Character
- which is why the .lcase()
method in this case cannot be invoked as a member method. Again, this is likely a bug in the runtime.
A month ago, I talked about upgrading my blogging platform to Adobe ColdFusion 2021; and, I've already been able to leverage this new character-array functionality. I'm using it to generate cryptographically secure tokens using the randRange()
function with the SHA1PRNG
algorithm:
<cfscript>
writeOutput( generateTokenValue( 20 ) );
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
/**
* I generate a cryptographically secure random token with the given length.
*/
public string function generateTokenValue( numeric tokenLength = 200 ) {
var letters = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
var letterCount = letters.len();
var parts = [];
for ( var i = 1 ; i <= tokenLength ; i++ ) {
parts.append( letters[ randRange( 1, letterCount, "SHA1PRNG" ) ] );
}
return( parts.toList( "" ) );
}
</cfscript>
Notice that I'm accessing the random letter using the letters[i]
array-notation. And, when we run this ColdFusion template a few times, we get the following cryptographically secure random tokens:
- RxMbzBOtUaWE2DJ8cL25
- 8EDL7BOgQowrz3QLx8aI
- KrJBzR6vx9BCkjAvEwmd
- 7U8ILIQ7irbOQdXsXbOo
Again, I don't know when this String-as-character-array functionality was added to Adobe ColdFusion; but, I'm excited to finally be able to use it!
Want to use code from this post? Check out the license.
Reader Comments
I've logged the
java.lang.Character
issue as a bug in the Adobe ColdFusion bug tracker:https://tracker.adobe.com/#/view/CF-4212577
Thanks as always for sharing your discoveries, Ben. As for this, yes, it's supported as of Cf2018 and up. Here's that last code running on trycf as 2018:
https://trycf.com/gist/75b3994be85aa042127e946905e29518/acf2018
and it fails if the url is changed to point to a cf2016. For some, that's all they need to see as the answer. (And I realize Ben is focused in these recent posts on just converting things to cf2021.)
For other folks, who may indeed wonder when something he points out may have changed in what engine version, I'd like to elaborate a bit to help them consider this approach to answering that question.
As surely Ben and many here know, trycf.com and cffiddle.org let you run (almost any) cfml, against different cf engine versions. They're (nearly) perfect for proving when such a change in language support happened.
Trycf.com supports CF 2021, 2018, 2016, 11, and even 10, as well as Lucee 5 and 4.5 and Railo 4.2, while Cffiddle.org, being from Adobe, supports CF 2021, 2018, and 2016. Each has its pros and cons, and I've been meaning to do a post on them, for those who may not know of either or both.
Of course, even for those who know of either, half the battle is remembering to try them in a case like this. :-) That's part of why I'm writing this. The other "half" of the battle (most of it) is having to create standalone code that can be used to test your question on either. Fortunately, the docs for CF and Lucee (and cfdocs.org) have largely been moving to using links to either of these showing running example code, to save us that trouble. Sometimes, that can be where we can easily see whether (or as of when) something we're interested in is supported.
In Ben's case, the last example can be seen running against cf2016 (where it fails) using this link:
https://trycf.com/gist/75b3994be85aa042127e946905e29518/acf2016
And the engine/version can be changed on that url or by using the gear icon in the trycf.com web ui. Again changing this to cf2018 (or 2021) shows that it works there. Yay. :-)
Hope that may help some folks. Again, I realize it's "obvious" to others. Since Ben's blog brings people together who have such wide ranges of experience, this seemed an opportune moment to mention the above.
@Charlie,
Great points all around. I have to say, I really like the fact that the Lucee CFML docs actually embed a TryCF iframe right in a lot of their function/tag pages. It's super helpful to see how things work.
Though, I have to admit that I usually use other people's TryCF examples - I am not good about creating my own. It's probably something I should look at doing more - it would make the examples palpable in a way they can't be with writing alone.
UPDATE: I realized that my demo here is flawed and misleading. While you can reference characters via bracket notation, my
for-in
demo doesn't actually work. When you do this:for ( var c in "hello" )
The loop runs, but
c=="hello"
- it runs as a single iteration for the entire value - it does not iterate over the letters in the string. I didn't test that this working properly, I just assumed that since it didn't break that it was running as I expected it to.I'll add a note to the top of the post as well.