Skip to main content
Ben Nadel at CF Summit West 2025 (Las Vegas) with: Nathaniel Emmert
Ben Nadel at CF Summit West 2025 (Las Vegas) with: Nathaniel Emmert

Code Kata: Formatting Compound Strings In ColdFusion

By
Published in

Earlier this week, in my Big Sexy Poems ColdFusion application, I added location information to my session list. The location information includes the city, region, and country code; which I compose using conditional commas and parenthesis based on the data that I have available. This kind of conditional composition and formatting never feels elegant. As such, I wanted to explore the topic of conditional formatting as a code kata in ColdFusion.

Brute Force

Historically, when implementing conditional composition and formatting of a compound string value such as name, location, address, etc, in ColdFusion, I just brute force it. Meaning, I have a series of if-blocks that conditionally build-up the value one check at a time.

To illustrate, let's use the brute force method to build up a user's name from the given prefix, first, last, and suffix parts. In the following ColdFusion code you'll see that each if-block:

  1. Checks if the current part is populated?
  2. Checks if the running value is populated?
  3. Conditionally includes a delimiter.
  4. Concatenates the part with the running value.

At the end, we'll output the composed string:

<cfscript>

	user = {
		prefix: "Mrs.",
		firstName: "Laura",
		lastName: "Badonka",
		suffix: "Jr."
	};

	// -- construct compound name. -- //

	name = "";

	if ( user.prefix.len() ) {

		name = user.prefix;

	}

	if ( user.firstName.len() ) {

		name &= name.len()
			? " #user.firstName#"
			: user.firstName
		;

	}

	if ( user.lastName.len() ) {

		name &= name.len()
			? " #user.lastName#"
			: user.lastName
		;

	}

	if ( user.suffix.len() ) {

		name &= name.len()
			? ", #user.suffix#"
			: user.suffix
		;

	}

	writeOutput( name );

</cfscript>

As you can see, the name value is incrementally built-up with each if check. And, when we run this ColdFusion code, we get the following output:

Mrs. Laura Badonka, Jr.

As you can see, most of the delimiters were space (); but, the last delimiter, prior to the suffix is comma-space (,). It's ugly, but it's easy to read and it works.

FAIL: List Append

As I was looking at my string concatenation, it occurred to me that what I'm really doing is building up a list of values using a variety of delimiters. I wanted to see if I could use ColdFusion's native list-functions to simplify this code.

In the following CFML code, we're doing the same exact thing; only, I'm leaning on ColdFusion to deal with the conditional delimiter:

<cfscript>

	user = {
		prefix: "Mrs.",
		firstName: "Laura",
		lastName: "Badonka",
		suffix: "Jr."
	};

	// -- construct compound name. -- //

	name = user.prefix;

	if ( user.firstName.len() ) {

		name = name.listAppend( user.firstName, " " ); // Space delimiter.

	}

	if ( user.lastName.len() ) {

		name = name.listAppend( user.lastName, " " ); // Space delimiter.

	}

	if ( user.suffix.len() ) {

		name = name.listAppend( user.suffix, ", " ); // Comma-Space delimiter.

	}

	writeOutput( name );

</cfscript>

This time, I'm not conditionally including the delimiter - I'm just telling the ColdFusion list-functions that a delimiter exists; and, I'm letting the list-functions conditionally include the delimiter if the list already has an item within it.

Unfortunately, this doesn't work. When we run this ColdFusion code, we get the following output:

Mrs. Laura Badonka,Jr.

The issue is subtle and I almost didn't notice it at first. If you look closely, however, you'll see that there's no space before the Jr. token. This is because ColdFusion's listAppend() function only uses the first character when a multi-character delimiter is supplied. So when I pass in comma-space (,) as the final delimiter, the native code only uses the comma.

Custom List Append Function

ColdFusion's listAppend() method didn't work; but I liked the concept. So my next thought was to build a ColdFusion User Defined Function (UDF) that borrowed the list concept but was tailored for a multi-character delimiter:

stringAppendIf( base, token, delimiter )

This UDF is basically performing a list-append with the two values (base and token), but will happily include the whole delimiter regardless of how long it is:

<cfscript>

	user = {
		prefix: "Mrs.",
		firstName: "Laura",
		lastName: "Badonka",
		suffix: "Jr."
	};

	// -- construct compound name. -- //

	name = user.prefix;
	name = stringAppendIf( name, user.firstName, " " ); // Space delimiter.
	name = stringAppendIf( name, user.lastName, " " ); // Space delimiter.
	name = stringAppendIf( name, user.suffix, ", " ); // Comma-Space delimiter.

	writeOutput( name );

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	/**
	* I append the token to the base using the given delimiter. If the token is empty, the
	* base is returned as-is.
	*/
	private string function stringAppendIf(
		required string base,
		string token = "",
		string delimiter = ""
		) {

		if ( ! token.len() ) {

			return base;

		}

		if ( ! base.len() ) {

			return token;

		}

		return "#base##delimiter##token#";

	}

</cfscript>

With this UDF, I don't need the if-blocks because the UDF will omit any token that's empty. And, when we run this ColdFusion code, we get the following output:

Mrs. Laura Badonka, Jr.

This time, space and comma-space where both included.

Custom Method With Optional Formatting

A user's name is relatively simple. But I want an approach that can be used for more complex data-sets. When I started to think about composing address values, I hit a snag. With an address, two issues arose:

  1. The "care or" has conditional formatting (including of a c/o).
  2. The city and state are on the same line.

The city/state issue wasn't really a bother since I can always nest the stringAppendIf() calls. But, the c/o issue was different. Including the c/o means that I have both a conditional inclusion and a conditional rendering of the "care of" value.

To deal with this, I updated the stringAppendIf() to accept an optional formatted token:

stringAppendIf( base, token [, formattedToken], delimiter )

This function will use the token to see if the value should be included; and, if so, it will use the formattedToken value as the composed part. In the following ColdFusion code note that in the careOf line, I'm using "c/o #careOf#" as the formatted token:

<cfscript>

	user = {
		prefix: "Mrs.",
		firstName: "Laura",
		lastName: "Badonka",
		suffix: "Jr."
	};
	address = {
		recipient = formatName( argumentCollection = user ),
		careOf: "Special Investigations",
		street1: "21 Jump Street",
		street2: "Suite 28A",
		city: "Metropolis",
		state: "Evergreen State",
		zipcode: "21001"
	};

	writeOutput( "<pre>" & formatAddress( argumentCollection = address ) & "</pre>" );

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	/**
	* I format the given name.
	*/
	private string function formatName(
		string prefix = "",
		string firstName = "",
		string middleName = "",
		string lastName = "",
		string suffix = ""
		) {

		var result = prefix;
		result = stringAppendIf( result, firstName, " " );
		result = stringAppendIf( result, middleName, " " );
		result = stringAppendIf( result, lastName, " " );
		result = stringAppendIf( result, suffix, ", " );

		return result;

	}


	/**
	* I format the given address as a multi-line string value.
	*/
	private string function formatAddress(
		string recipient = "",
		string careOf = "",
		string street1 = "",
		string street2 = "",
		string city = "",
		string state = "",
		string zipcode = "",
		string linebreak = chr( 10 )
		) {

		var result = recipient;
		result = stringAppendIf( result, careOf, "c/o #careOf#", linebreak );
		result = stringAppendIf( result, street1, linebreak );
		result = stringAppendIf( result, street2, linebreak );
		result = stringAppendIf( result, stringAppendIf( city, state, ", " ), linebreak );
		result = stringAppendIf( result, zipcode, linebreak );

		return result;

	}


	/**
	* I append the token (or optionally the formatted token) to the base using the given
	* delimiter. If the token is empty, the base is returned as-is.
	*/
	private string function stringAppendIf(
		required string base,
		string token = "",
		string formattedToken = "",
		string delimiter
		) {

		// The formatted token is optional and allows the token to be given additional
		// formatting if - and only if - it's populated. If the delimiter is null, we'll
		// assume that the formatted token has been omitted and is acting as the delimiter.
		if ( isNull( delimiter ) ) {

			// Shift arguments over.
			delimiter = formattedToken;
			formattedToken = token;

		}

		// We're using the token, NOT the formatted token, to determine the control flow.
		if ( ! token.len() ) {

			return base;

		}

		if ( ! base.len() ) {

			return formattedToken;

		}

		return "#base##delimiter##formattedToken#";

	}

</cfscript>

This code is longer because I'm doing more stuff; but hopefully it's not too hard to follow. When we run this ColdFusion code, we get the following output:

Mrs. Laura Badonka, Jr.
c/o Special Investigations
21 Jump Street
Suite 28A
Metropolis, Evergreen State
21001

Notice that "care of" line is prefixed with c/o.

Fluent API Wrapper

One thing I really miss about the listAppend() approach is the fact that they are member methods. In my previous approach, I'm constantly having to pass-around the running value; and it looks quite noisy. In this final approach, I wanted to wrap the stringAppendIf() calls in a light-weight wrapper that can provide that fluent, implied-context API.

I'm creating this fluent wrapper with a UDF, stringAppendPipeNew():

<cfscript>

	/**
	* I create a piping version of the stringAppendIf() function.
	*/
	private struct function stringAppendPipeNew( string initialValue = "" ) {

		var this = {
			value: initialValue,
			appendIf: function( token, formattedToken, delimiter ) {

				this.value = stringAppendIf(
					base = this.value,
					token = token,
					formattedToken = arguments?.formattedToken,
					delimiter = arguments?.delimiter
				);

				return this;

			}
		};

		return this;

	}

</cfscript>

This ColdFusion function returns a struct that contains a closure function. This closure function allows the appendIf() logic to refer back to itself using the closed-over this reference. And, all this function does is proxy the stringAppendIf() UDF, passing in the running this.value as the implied first argument.

Here's the full code:

<cfscript>

	user = {
		prefix: "Mrs.",
		firstName: "Laura",
		lastName: "Badonka",
		suffix: "Jr."
	};
	address = {
		recipient = formatName( argumentCollection = user ),
		careOf: "Special Investigations",
		street1: "21 Jump Street",
		street2: "Suite 28A",
		city: "Metropolis",
		state: "Evergreen State",
		zipcode: "21001"
	};

	writeOutput( "<pre>" & formatAddress( argumentCollection = address ) & "</pre>" );

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	/**
	* I format the given name.
	*/
	private string function formatName(
		string prefix = "",
		string firstName = "",
		string middleName = "",
		string lastName = "",
		string suffix = ""
		) {

		return stringAppendPipeNew()
			.appendIf( prefix )
			.appendIf( firstName, " " )
			.appendIf( middleName, " " )
			.appendIf( lastName, " " )
			.appendIf( suffix, ", " )
			.value
		;

	}


	/**
	* I format the given address as a multiline value value.
	*/
	private string function formatAddress(
		string recipient = "",
		string careOf = "",
		string street1 = "",
		string street2 = "",
		string city = "",
		string state = "",
		string zipcode = "",
		string linebreak = chr( 10 )
		) {

		return stringAppendPipeNew()
			.appendIf( recipient, linebreak )
			.appendIf( careOf, "c/o #careOf#", linebreak )
			.appendIf( street1, linebreak )
			.appendIf( street2, linebreak )
			.appendIf( stringAppendIf( city, state, ", " ), linebreak )
			.appendIf( zipcode, linebreak )
			.value
		;

	}


	/**
	* I create a piping version of the stringAppendIf() function.
	*/
	private struct function stringAppendPipeNew( string initialValue = "" ) {

		var this = {
			value: initialValue,
			appendIf: function( token, formattedToken, delimiter ) {

				this.value = stringAppendIf(
					base = this.value,
					token = token,
					formattedToken = arguments?.formattedToken,
					delimiter = arguments?.delimiter
				);

				return this;

			}
		};

		return this;

	}


	/**
	* I append the token (or optionally the formatted token) to the base using the given
	* delimiter. If the token is empty, the base is returned as-is.
	*/
	private string function stringAppendIf(
		required string base,
		string token = "",
		string formattedToken = "",
		string delimiter
		) {

		// The formatted token is optional and allows the token to be given additional
		// formatting if - and only if - it's populated. If the delimiter is null, we'll
		// assume that the formatted token has been omitted and is acting as the delimiter.
		if ( isNull( delimiter ) ) {

			// Shift arguments over.
			delimiter = formattedToken;
			formattedToken = token;

		}

		// We're using the token, NOT the formatted token, to determine the control flow.
		if ( ! token.len() ) {

			return base;

		}

		if ( ! base.len() ) {

			return formattedToken;

		}

		return "#base##delimiter##formattedToken#";

	}

</cfscript>

If we run this ColdFusion code, we get the same output:

Mrs. Laura Badonka, Jr.
c/o Special Investigations
21 Jump Street
Suite 28A
Metropolis, Evergreen State
21001

With the fluent-API wrapper, I feel like I almost don't even need the formatName() and formatAddress() functions anymore. The add almost no value to the fluent-API itself, I might as well just use it directly. That said, a reusable abstraction is always nice to have.

It's All Trade-Offs

My first approach (brute force) and my last approach (fluent API) are wildly different. But neither is the "right" approach. Each of these approaches comes with its own trade-offs. There's something to be said about the sheer readability of the brute force approach - top down, imperative awesomeness. The fluent API creates tighter, less noisy code; but, it's also harder to understand.

I'm definitely curious to know what kind of formatting techniques everyone else is using.

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

Reader Comments

Post A Comment — I'd Love To Hear From You!

Post a Comment

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
Managed hosting services provided by:
xByte Cloud Logo