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

SanfordWhiteman
Level 10 - Community Moderator
Level 10 - Community Moderator

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 with field with values “on” and “off”.

and

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 (or no longer) working with a Boolean.

Boolean vs. sorta-boolean-ish-sometimes

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” or 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 radio buttons on a form that map to a Boolean on the back end…

    values associated with a custom checkbox-lookalike field

    workarounds because some aspect of an app doesn't support Booleans…

    values that convert predictably to/from Booleans in a specific framework or language…

    the way an app displays 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 set to 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 (CO or Person fields) require special treatment once they get into Marketo’s Velocity, 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.

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.[3]

TRUTHINESS

This is a big one. Some 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 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 — the ANSI SQL standard, not all products based on that standard — does have a column type BOOLEAN. However, of the big 5 SQL products, only Postgres supports it. Microsoft SQL, MySQL, Oracle, and DB2 don’t. So, to avoid database vendor lock-in, you must use some other SQL type to store 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!

20495
2
2 Comments
Adam_Blinzler2
Level 2

I might be miss understanding. I have a CO with a Boolean field of display Custom Object and a Custom Field for Is Partner that is a Boolean field. The logic doesn't work when I change the match to == true or == "true" but does work when I use == "1" as a true value. I haven't used .equals() and tested that syntax but I would think they be the same as ==. 

Here is a snippet of the code: (I used your date converter from another post so that I can utilize a time frame. Thank You so much for all of your amazing posts)

Line 12: #if( $lead.Is_Partner == "1")

Line 33: #if($partnerEndUserCompany_cList.get($i).displayCustomObject == "1")

pastedImage_1.png

pastedImage_2.png

Pardon my poor scripting

##SETUP DATE CONFIG/CONSTRAINTS  
#set( $defaultTimeZone = $date.getTimeZone().getTimeZone("America/Los_Angeles") )   
#set( $defaultLocale = $date.getLocale() )   
#set( $calNow = $date.getCalendar() )   
#set( $ret = $calNow.setTimeZone($defaultTimeZone) )   
#set( $calConst = $field.in($calNow) )   
#set( $ISO8601 = "yyyy-MM-dd'T'HH:mm:ss" )   
#set( $ISO8601Date = "yyyy-MM-dd" )  


##THE WORK
#if( $lead.Is_Partner == "1")


#set($productKeyCount = $partnerEndUserCompany_cList.size() - 1)
#foreach ($i in [$productKeyCount .. 0])
 
#set($expDays = $convert.toCalendar(   
  $convert.parseDate(   
    $partnerEndUserCompany_cList.get($i).expirationDate,    
    $ISO8601Date,
    $defaultLocale,    
    $defaultTimeZone
  )   
) )   

#if( $calNow.after($expDays) )

#set( $diffRemaining = $date.difference($calNow,$expDays) )
#set( $daysRemaining = $convert.toInteger($diffRemaining.getDays()) )

#if( $daysRemaining >= -90)
#if($partnerEndUserCompany_cList.get($i).displayCustomObject == "1")

#if($partnerEndUserCompany_cList.get($i).expirationDateDisplayed == "")

${partnerEndUserCompany_cList.get($i).expirationDate}

<hr>
#else
#set($expirDate = "${partnerEndUserCompany_cList.get($i).expirationDateDisplayed}
<hr>")

#if( $expirDate.contains("expirationDateDisplayed"))

#set( $expirDate = "<br/><hr/>")


#end
${expirDate}

#end

#end
#end
#end
#end
#else
${lead.expirationDate}

#end
‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
SanfordWhiteman
Level 10 - Community Moderator

This post is about Integer in the database → real Boolean in Velocity.

You're referring to Boolean in the database → real Boolean in Velocity.

And this itself has two different sub-cases: Boolean in the Lead database, and Boolean in the Custom Object database. 

      
          

      
  • a Boolean on a CO is serialized as the string "1" or the string "0"
  •   
  • a Boolean on a Lead is serialized as the string "1" or the string ""

See also: Velocitip: Convert was-Boolean, now-String → Number, then enjoy $display.message 

You should always  use .equals() to do comparisons in Velocity; == has some edge cases that will create unexpected results but .equals() is completely predictable.

You can also convert your stringified real Boolean with this:

#set( $actualBoolean = $convert.toBoolean($display.plural($convert.toIntegers($stringifiedBoolean)[0],"true")) )

It will work for both the CO and Lead variants.