Converting Integer 1/0 to Boolean true/false in Velocity

New developers (and non-developers) often mistake values that might be converted to, converted from, or treated sort of like Booleans for a language’s actual Boolean type.

This leads to internal contradictions like:

I have a Boolean field with values “on” and “off”. If my Boolean is set to 1, I want to do this.
If it’s set to 0, do this other thing.

If the value is really a string like “on” or a number like 1, you’re not working with a Boolean (even if somewhere in an app, the word “Boolean” was used/misused).

Boolean vs. sorta-boolean-ish-sometimes

I’m not faulting folks for being confused: “overnight” guides and 0-to-60 boot camp fantasies love to label core technical knowledge as esoteric or unimportant.

But in general, if a programming language includes a Boolean type, then a Boolean variable can have one of two canonical [1] values:

  • one value will be the unquoted keyword true
  • the other will be the unquoted keyword false

The keywords might be lowercase (true/false), uppercase (TRUE/FALSE), or mixed-case (True/False); some languages are case-insensitive.

But the quoted strings “true” and “false”, the numbers 0 and 1, the numeric strings “0” and “1”, let alone strings like “Yes” and “No” or “Y” and “N” or “opt-in” and “opt-out” and the empty string “” — with rare exceptions in the world of SQL[2] (but not in Marketo) none of those are Boolean values.

Sure, those pairs of values might be a lot of useful things:

  • labels for a pair of radio buttons on a form that map to a Boolean on the back end…
  • values associated with a custom checkbox-lookalike field
  • workarounds necessary because some component of an app doesn't support Booleans…
  • values that convert predictably to/from Booleans in a specific framework or language…
  • the way an app prints Booleans in emails or web pages (hint, hint)

But not real Booleans, and the difference matters.

Just one of the reasons it matters in Marketo-land

A recent Velocity question from a client brought the difference into high relief:

With our Product Registration CO, how come the condition #if($product.isRecalled) always seems to match, even if isRecalled is false?

I immediately looked at their Custom Object setup in Admin, and check it out:

Bingo! The field isn’t a Boolean. It’s an Integer, presumably sent to Marketo exactly as it’s stored in their in-house SQL database. And they were trying to use it in a Boolean context (an #if() condition) and expecting 1 and 0 to be truthy and falsy respectively.

But Velocity doesn’t work that way. (Java doesn’t work that way, either: in Java, a strictly typed language, if something ain’t a Boolean, you’ll get a fatal type mismatch error, truthiness and falsiness do not apply.)

Note: even real Boolean fields require special treatment once they get into Marketo’s Velocity (different treatment for Person and CO fields, at that), as I’ve written about before. But here the field wasn’t even a Boolean to begin with! I’ll show you the slickest way to do the required conversion in Velocity in a moment.

I’ll show you the slickest way to do the required conversion in Velocity in a bit. But first, a little more about real Booleans vs. boolean-ish values, to help you code and/or talk to coders.

Truthiness doesn’t change the truth

I don’t blame folks for thinking 0 and 1 are low-level Boolean values at first. There are a bunch of things that can lead you down that path.

Mislabeling

Many SaaS apps don’t do you any favors by calling anything vaguely yes-ish/no-ish a “boolean” to sound cool (I guess?). It would be better to call such things “YepNopes” than to be misleading about the real type.

The String-centric Web

Bedrock web technologies operate exclusively on strings. Take a standard x-www-form-urlencoded form post or a URL query param: those are all characters on the wire.

So there’s a disconnect: you have a form with a checkbox (which you can programmatically control via the real Boolean property checked in JavaScript). It’s going to eventually write to a real Boolean on a faraway server. But to get there you have to send a string. The intermediate code that converts the string “optedin=nope” to a Boolean optedin with value false may be 100% reliable, but that doesn’t make less confusing.[3]

Truthiness

This is a big one. Some programming languages assign “truthiness” or “falsiness” to certain non-Boolean values, so they don't have to be manually converted to the Boolean datatype to be used in a Boolean context. This can make for less complex-looking code (for what that’s worth) but with a cost of risk and just bad learnin’.

Make no mistake: just because a language considers if(1) or if("1") to be true, that doesn’t mean 1 and “1” are Booleans. They’re merely treated, on-the-fly, as temporary Boolean true or false. And a value that’s temporarily truthy in an if() condition may not even be true when permanently converted using toBoolean(), bizarre as that might seem!

Moreover, truthiness rules are totally different across languages. One language’s truthy is another language’s falsy — or another language’s fatal TypeError. I’m not harping on hard-to-find cases: 0 and empty “” are falsy in JavaScript; they're both truthy in Velocity.

The elusiveness of SQL BOOLEAN

SQL databases are probably the main way people come to think 0 and 1 are Booleans, when in fact they’re just a very common way of storing things that were/will be Booleans.

SQL — talking about the ANSI SQL standard, not all products based on that standard — does have a column type BOOLEAN. However, this type is optional in the standard, so you can make a “SQL database” yet not support it. Indeed, of the big 5 SQL products, only Postgres supports BOOLEAN. Microsoft SQL, MySQL, Oracle, and DB2 do not.

Therefore, to avoid database vendor lock-in, you must use some other SQL type to represent values that will be converted to/from Booleans in your app code. The narrowest possible INT, with values restricted to 0 and 1 via a constraint, is very widely used. (Though it’s not the only choice.)

Back to Marketo

You know I couldn’t resist that technical dive. But let me flip modes and get back to the matter at hand.

Velocity is more forgiving than Java, so it doesn’t throw an error[4] when non-Booleans are used in a Boolean condition. Velocity uses a very simple rule: anything other than null or the real Boolean false is truthy. That means an empty String, a non-empty String, an Object or List (regardless of whether it has any properties/items), and 0 and 1 and all other Integers are all going to match #if($something)!

Now it’s clear why the client’s attempt to use #if($product.isRecalled) was doomed to fail.

So what’s the simplest way to turn 1/0 to true/false?

There’s clearly a plodding way to do it:

#if( $product.isRecalled.equals(0) )
#set( $product.isRecalledBoolean = true )
#else
#set( $product.isRecalledBoolean = false )
#end

But that’s going to be cumbersome to call every time. What about converting the values to Booleans using ConvertTool?

#set( $product.isRecalledBoolean = $convert.toBoolean($product.isRecalled) )

Nope. ConversionTool’s toBoolean() only treats the String "true" as convertible to Boolean true. Numbers 0 and 1 will both convert to Boolean false. (Notice how that’s the opposite of 0 and 1’s truthiness? That’s the kind of unpredictability I’m talking about!)

But there’s a secret weapon we can use to keep the conversion to a (slightly longer) one-liner: DisplayTool’s plural() method.

DisplayTool.plural() would be better called DisplayTool.notOneOrMinusOne(), because 0 is considered plural:

This quirk is great for our case, because now we can do:

#set( $product.isRecalledBoolean = $convert.toBoolean($display.plural($product.isRecalled,"true"))

Here’s how this one-line contraption works:

  • plural checks if the first argument ($product.isRecalled) is what it considers “singular” or “plural”
    • if the argument is singular (that is, for our purposes, if it’s 1 – since we’re saying it’s never anything other than 0 or 1) then it returns the second argument, the string "true"
    • if the argument is plural (that is, if it’s 0) then it returns the second argument appending the letter “s”, to make the string "trues"
  • $convert.toBoolean then processes the returned value, which is always "true" or "trues"
    • toBoolean("true") converts to Boolean true
    • toBoolean("trues") converts to Boolean false

So we’ve implemented the desired 0→false, 1→true conversion. Cool, eh?


Notes

[1] “Canonical” means a value that can be both written to and read from the variable. I use this qualifier because some languages allow alternate values to write a Boolean, but you’ll never see those values when you read a Boolean. For example, in XML, 0 and 1 are synonyms for true and false when setting an xs:boolean. But the values read back out are always true or false. And the Postgres example below applies as well.

[2] Postgres goes bizarrely further than just the standard TRUE/FALSE keywords (though those are the preferred values for setting SQL BOOLEANs). They allow lots of aliases to be used for setting values: the strings “true”/“false” and “t”/“f”, even “yes”/“no” and “on”/“off” and a few more. Stranger still IMO is that the output values are the strings “t” and “f”. So you would say the canonical values are “t” and “f” in that particular dialect (since those are the only values that can be both written and read). Weird exception.

[3] Funny to call XML “newer” than anything as it’s so ancient, but both XML and JSON reserve special Boolean values (as well as distinguishing numbers from numeric strings) even though the overall encoding is still character-based. But regular form POSTs and GET params are strings through and through, until/unless the server chooses otherwise.

[4] This forgiveness is not necessarily a good thing. As I’ve droned on about before, Velocity suppresses a lot of fatal errors to keep your app running, but not all types by any means: you can trivially cause a fatal error by .get()-ing the 1st item in an empty List! And even when an error doesn’t stop Velocity from running, that doesn’t mean the line that caused the error even did anything.