Skip to main content
Ben Nadel at cf.Objective() 2012 (Minneapolis, MN) with: Erik Meier and Jesse Shaffer and Bob Gray
Ben Nadel at cf.Objective() 2012 (Minneapolis, MN) with: Erik Meier Jesse Shaffer Bob Gray

Lists - The Unsung Heroes Of ColdFusion And Lucee CFML

By on
Tags:

When you first start programming in ColdFusion, you tend to lean very heavily on the idea that "everything is a String". Then, as you become more experienced, you learn that String-manipulation is relatively slow; and, you start to use more complex data structures like Arrays and Structs where possible. But, as I was reminded yesterday in a conversation with fellow InVsion engineer, Shawn Grigson, Strings - and more specifically Lists - are an amazing part of the ColdFusion runtime. In fact, I'd go so far as to say they are the unsung heroes of the ColdFusion and Lucee CFML worlds. As such, I thought it would be fun to reflect on where I use lists in my day-to-day ColdFusion programming.

In ColdFusion, there's no explicit List data type. Every String is implicitly a List. And, every List is implicitly a String. The ColdFusion runtime is just providing the concept of a List on top of every String value using just-in-time method bindings. I don't quite understand how the runtime does it - it just works.

NOTE: When I talk about "just in time" bindings, I'm talking about member-methods, like value.listFirst(). There are, of course, globally available, built-in functions like listFirst() as well.

What makes a String a "list" is the fact that it contains a character - or set of characters - that can be used to delimit items within that list. And what really makes lists to powerful is the fact that the delimiter can be anything. Once you get past the default delimiter - the comma - you can start to see lists everywhere.

Child from Sixth Sense meme: I see lists

What follows are example of where I see lists in ColdFusion.

ASIDE: Everything you can do with a list you can also do with data-type conversion and Array manipulation. The point here isn't that Lists unlock new behavior - it's that they unlock wonderful developer ergonomics.

Naively Parsing Names

At InVision, we store the user's name as a single, free-text field as is considered the best practice in software development. However, when we interface with external systems, such as payment processors, we are often forced to provide the user's name using archaic "First" and "Last" name concepts. To make sure those API calls don't break, we can treat the user's name as a space-delimited list:

<cfscript>

	include "./utils.cfm";

	name = "Ben Nadel";

	// Treating names as a space-delimited list is a super naive way to handle names.
	// Because, names are quite complicated - and if a user hasn't explicitly told you
	// which is first and which is last, you'll likely get it wrong. But, IN A PINCH,
	// this can be helpful. Such as when having to convert a single-field within your app
	// into multiple fields required by an external systems (such as a payment processor).
	echoLine( name.listFirst( " " ) );
	echoLine( name.listRest( " " ) );
	echoLine();

	name = "Madonna";

	// The lovely thing about listRest() is that it will just return an EMPTY string if
	// the given list only has one item in it.
	echoLine( name.listFirst( " " ) );
	echoLine( name.listRest( " " ) );

</cfscript>

And, when we run this ColdFusion code, we get the following output:

Ben
Nadel

Madonna

Notice that Madonna didn't get doubly-output despite not having any spaces. That's the magic of the listRest() function - it will just return the empty-string if the list only has one item!

Of course, attempting to parse a name using spaces is a fool's errand - just ask my wife, "Mary Kate", whose first name contains a space. That said, in a pinch, when required to adhere to archaic API signatures, it at least prevents errors.

Parsing Email Addresses

Email addresses can be seen an @-delimited list, in which the @ separates the local-part / recipient from the domain name. This can be helpful if you need to run special logic during a sign-up routine based on a user's email domain:

<cfscript>
	
	include "./utils.cfm";

	email = "ben@bennadel.com";

	// Treating email as an @-delimited list makes plucking the local-part / recipient
	// and the domain super easy!
	echoLine( email.listFirst( "@" ) );
	echoLine( email.listRest( "@" ) );

</cfscript>

And, when we run this ColdFusion code, we get the following output:

ben
bennadel.com

Parsing Key-Value Pairs Such As A Query-String

Sometimes, a list has two delimiters. For example, a URL query-string can be thought of a series of =-delimited pairs that are, themselves, &-delimited. We can easily parse a URL query-string by first iterating over the high-level list and then parsing each pair:

<cfscript>

	include "./utils.cfm";

	queryString = "foo=bar&quote=soccer=life";

	// To iterate over a query-string, we can use the list-based loop functionality
	// because a query-string is nothing but an &-delimited list.
	loop
		value = "pair"
		list = queryString
		delimiters = "&"
		{

		// And, for each item within the query-string, we get a pair of values that are
		// really just a =-delimited list.
		echo( canonicalize( pair.listFirst( "=" ), true, true ) );
		echo( " &rarr; " );
		echoLine( canonicalize( pair.listRest( "=" ), true, true ) );

	}

</cfscript>

One of the beautiful things about ColdFusion is that list iteration is a first-class citizen of the language. As you can see here, we're using the CFLoop to efficiently iterate over the &-delimited portion; then, using the List-functions to split the key-value pairs.

And, when we run this ColdFusion code, we get the following output:

foo → bar
quote → soccer=life

Parsing URLs

To take the query-string concept one level higher, we can think of a URL as another type of list that has multiple fragments. In this case, the "#" separates the "fragment" from the server; and the "?" separates the route from the query-string. We can pluck these pieces out using our handy list functions:

<cfscript>

	include "./utils.cfm";

	siteUrl = "https://www.bennadel.com/index.cfm?site-photo=305##main-content";

	// A URL can be seen as a list that has "?" and "#" delimiters.
	serverPart = siteUrl.listFirst( "##" );
	fragment = siteUrl.listRest( "##" );
	resource = serverPart.listFirst( "?" );
	queryString = serverPart.listRest( "?" );

	echoLine( resource );
	echoLine( queryString );
	echoLine( fragment );

</cfscript>

And, when we run this ColdFusion code, we get the following output:

https://www.bennadel.com/index.cfm
site-photo=305
main-content

Extracting Subdomains

A domain name can be thought of as a .-delimited list. Which means, extracting the subdomain can be as simple as getting the first item in that list (assuming we're not dealing with a multi-level subdomain):

<cfscript>

	include "./utils.cfm";

	domain = "app.bennadel.com";

	// A domain can be seen as a .-delimited list of tokens.
	echoLine( domain.listFirst( "." ) );
	echoLine( domain.listRest( "." ) );

</cfscript>

And, when we run this ColdFusion code, we get the following output:

app
bennadel.com

Getting The User's IP-Address Inside A Load-Balancer

In a simple web-application setup, getting the user's IP address is as simple as accessing cgi.remote_addr. However, once you move to any kind of load-balanced setup or have a reverse-proxy, what you'll find is that cgi.remote_addr actually contains the IP-address of the ingress / proxy - not the IP-address of the user.

In these situations, the user's IP-address is often passed-through using an HTTP Header such as X-Forwarded-For. And, this value is a comma-delimited list of each IP in the layers of physical architecture. Which means, to access the user's IP-address, we usually need to extract the first IP in that list:

<cfscript>

	include "./utils.cfm";

	// When your application is sitting behind several layers of ingress / load-
	// balancers, the user's original IP is usually passed-through as an HTTP header
	// (often "X-Forwarded-For"), which is really just a comma-delimited list of address,
	// the first of which is the user's actual IP address.
	ip = request[ "X-Forwarded-For" ] = "1.1.1.1,2.2.2.2,3.3.3.3,4.4.4.4";

	echoLine( ip.listFirst() );
	echoLine( ip.listRest() );

</cfscript>

And, when we run this ColdFusion code, we get the following output:

1.1.1.1
2.2.2.2,3.3.3.3,4.4.4.4

Parsing HTTP Authorization Headers

When a client authenticates against an API using something like Basic authentication or Bearer authentication, the client provides an Authorization HTTP header. This header value can (usually) be thought of as a space-delimited list. And, in the case of Basic authentication, the token within the header can subsequently be thought of as a colon-delimited list:

<cfscript>

	include "./utils.cfm";

	authorization = "Basic YW5uYTppY2FuaGF6Y2hlZXNlYnVyZ2Vy";

	// The Authorization header is really just a space-delimited list.
	echoLine( authorization.listFirst( " " ) );
	echoLine( authorization.listRest( " " ) );
	echoLine();

	// The Basic authentication, the payload is a Base64-encoded string that contains
	// the username and password a colon-delimited list.
	base64token = authorization.listRest( " " );
	binaryToken = binaryDecode( base64token, "base64" );
	token = charsetEncode( binaryToken, "utf-8" );

	echoLine( token.listFirst( ":" ) );
	echoLine( token.listRest( ":" ) );

</cfscript>

And, when we run this ColdFusion code, we get the following output:

Basic
YW5uYTppY2FuaGF6Y2hlZXNlYnVyZ2Vy

anna
icanhazcheeseburger

Extracting File Extensions

When processing files, you may need to validate or perform processing based on the file-type. And while you might be best served detecting file-types using magic numbers / BOM / Byte-Order-Marks, sometimes all you need is the file extension. And, if we look at the clientFilename as a dot-delimited list, this becomes trivial:

<cfscript>

	include "./utils.cfm";

	clientFilename = "lucy-the-goose.gif";

	// If you need to extract the file-extension from a file-name, then you can think of
	// the file as being a dot-delimited list.
	echoLine( clientFilename.listLast( "." ) );

</cfscript>

And, when we run this ColdFusion code, we get the following output:

gif

Lists Are Everywhere In ColdFusion

The above examples are just the ones I could think of off the top of my head. But, lists are everywhere in ColdFusion once you know how to look for them. File paths, phone numbers, date-strings, time-strings, Redis keys, form submissions - and it just goes on from there. In theory, String manipulation is slow. But, in practice, it won't make a difference - don't over-think it. Focus on the developer ergonomics of what the List pseudo-data-type can offer.

Want to use code from this post? Check out the license.

Reader Comments

6 Comments

I was wondering about a space in the first name.
My wife is Sarah Bell, and we named all of the kids with 2 first names ( space ) and a middle name ( starting with S ) and their last name, which are not all the same, due to, history.

Maybe this is our way of punishing our children in advance, because we suffered, so must you :D

Thanks for all you do!!!

15,674 Comments

@Gavin,

Ha ha, my wife is the same way, "Mary Kate". I can't tell you how often people don't even know to say her name as a two-part name, forget about trying to parse it programmatically :D

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel