Skip navigation
All Places > Products > Blog > 2019 > October
2019

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!

*** Posted on behalf of Marketo Champion Ajay Sarpal, Consultant - Martech and Sales Operations.***

 

In my 20+ years of professional experience with big brands, mid-tier and start-ups, just like each marketer I was also asked by my previous CMOs on:
• How to find out our best performing campaigns or channels?
• Which channels or programs are influencing top of the funnel, bottom of the funnel and middle of the funnel?
• What channels are driving the most leads?
• What channels are driving the most opportunities?
• What channels are ultimately driving the most revenue?
• Sales team keeps complaining that Marketing is not influencing the opportunities. Lead quality is questionable and lead volume is low. How to generate qualified leads? Which channel or marketing source is best in winning the opportunities? How to addresses the misalignment between sales and marketing teams?
• How much revenue is being generated from our offline channels/online channels/landing pages/content?
• Allocate marketing funds to top-performing channels
• How to forecast marketing goals in relation to revenue?

 

Bizible helps us in getting all the above stated questions answered. Although there are many Marketing Attribution tools available in the market but with my experience Bizible is one of the best attribution platform because you can automate many tasks by efficiently using UTM parameters and it helps in streamlining the process by avoiding creation of multiple campaigns for each and every channels for the same program. Bizible has an edge over competitors as not many attribution providers share the details of “First Touch” which is before lead creation touchpoint. It is scalable and builds meaningful and insightful reports for better aligning sales and marketing teams. Bizible helps in bringing transparency, finding out top or low performing channels or programs and in the end provides insights in generating more qualified leads for your organization. Bizible automatically tracks and reports on channel performance, providing visibility into which channels are driving the most customer engagement and allowing you to optimize your marketing spend accordingly.


Touchpoint Details: The structure of Bizible’s attribution models reflects the four major touchpoints that occur in the customer journey:
• First Touch (FT)
• Lead Creation (LC)
• Opportunity Creation (OC)
• Closed-Won deal (CW)

 

Single-touch Attribution Models which attributes 100% of the attribution Credit:

The First Touch model only focuses on the very first interaction a lead has with your organization. This model attributes 100% of the attribution credit to the first time the lead became aware of your company, the First Touch (FT).

 

The Lead Creation model attributes 100% of the attribution credit to the LC touchpoint, when a prospect provides their contact information and becomes a lead.

 

Multi-Touch Attribution Models – Position Based models are explained below:

U-Shaped Model: The U-Shaped model focuses on both the FT and LC touchpoints.

W-Shaped Model: Three of the milestone touchpoints are included in the W-Shaped model. In this model, the FT, LC, and OC touchpoints are each attributed 30% of the attribution credit. The remaining 10% is attributed proportionally to any intermediary touchpoints that occur between the three milestone touchpoints.

Full Path Model:  The full path model includes all four milestone touchpoints. FT, LC, OC and CW are each given 22.5% of the revenue credit, and the remaining 10% is distributed equally among the intermediary touches.

Marketing Attribution Insights after implementing Bizible:

The above picture shows the complete Sales Cycle starting with FT, LC, OC and CW marketing touches. The picture explains:
Anonymous First Touch is from Google Adwords.
Lead Creation Touch is from LinkedIn Blog Post.
Opportunity Creation Touch is from Pricing Page
Closed-Won Deal Touch is from Sales Dinner.

 

Various ways to get the insights from Bizible: 

Bizible Dashboards (Discover)

  • Bizible Boards:
  • Overview provides the most comprehensive view of your marketing performance, helping marketing teams make the right decisions when growing your team, budget, or revenue.
  • Growth displays a quick overview of all key marketing metrics in aggregate as well as a charted trend over time, allowing you to easily see the peaks and valleys in your performance.
  • ROI
  • Account-Based Marketing is made for marketers who focus their efforts on a list of targeted accounts. See how well you're hitting those persons, opportunities, and accounts.
  • Marketing Spend displays a thorough and aggregated view of your team's marketing spend across channels and campaigns, so you can use it to determine your upcoming budget.
  • Role- Based Views:
  • CMO targets the head of the Marketing team, displaying a comprehensive view of your team's marketing execution and can be used to determine which channels or teams are over or under performing.
  • Marketing Ops
  • Journey:
  • Velocity can help both Marketing and Sales determine how long the overall sales cycle is taking or visually see which stages or channels are slowing down your pipeline to optimize velocity for future opportunities.
  • Snapshot gives you a view that no other platform can provide. View the state of your CRM at any given point in time, with the distribution of records across Lead/Contact and Opportunity stages.
  • Passport presents a view of all your Leads/Contacts and Opportunities that have gone through each pipeline stage within a given time frame.
  • Engagement Path

 

CRM Reports:

The beauty of Bizible is that you can build custom reports within the CRM Systems. In Salesforce, we can build following reports and if needed, we can customize the reports by adding the fields from your CRM by creating or editing Custom Report types:

 

Salesforce Reports:
• Leads with Bizible Touchpoints (Custom)
• Bizible Person with Bizible Touchpoints (Custom)
• Opportunities with Bizible Attribution Touchpoint (Custom)

 

You can also create a few cool reports:
o Top 5 Keywords converting to Sales Opportunities
o Top 5 Channels for Closed Revenue
o Top 5 Landing pages by revenue won or opportunity created
o Top 5 Social campaigns

 

 

With Bizible set up, you can now unlock the power of Marketing Attribution!

Community user JW asked a surprisingly unprecedented (AFAIK) question:

 

I am looking to send out a communication to our customers that includes that persons Policy Number. Unfortunately our legal department will not let us send the entire {{lead.Policy Number}} digitally. We are permitted to send the last 4 digits however...  with Velocity Scripting can I get it to show as *****3253?

 

Let’s be clear: this isn’t relevant to credit card numbers or SSNs because those fields would never be in Marketo in the first place! (Right, guys?)

 

But other fields – like Policy Number here, or other account/lead info like the person’s phone number – legitimately would be present in Marketo and your CRM, yet might be nice to partially redact in emails. In the unlikely event that an email is intercepted, the person’s privacy is protected, but there’s still enough data to know they’re the right recipient.

 

So here’s a quick one-liner to mask out 1234567890 to ******7890:

 

${lead.PolicyNumber.replaceAll(".(?=.{4,}$)","*")}

 

In brief, it’s a regex lookahead and replace. The pattern matches any character that has 4 or more characters between it and the end of the string, ergo, all characters except the last 4.

 

(Yes, there are ways to do this with indexOf and substring instead, possibly more efficiently than the regex… if you’re timing to the microsecond, that is. But as always with Velocity, saving a few lines of code is worth a few μs.)

***Posted on behalf of Kimberly Galitz, Marketing Automation and Attribution Specialist at Bandwidth.*** 

 

One of the (many) advantages to using Bizible is the ability to track your marketing efforts from both online AND offline channels, giving visibility into the performance of channels where it may not have been available before.

As cut and dry as online vs. offline may seem, it can be complicated to determine when things should be associated with an offline vs. online channel, and beyond that - which actual engagement should count as a touchpoint.

Most simply put, online channels are any channels that would be directly associated with your website or any integrated site – any display advertising, paid search, paid social, organic search, organic social, email, chatbots, etc.

Offline channels are associated with initiatives like direct mail, trade shows or hospitality events, marketing or sales research, and pretty much any other channel where a person’s engagement cannot be tracked digitally. The best way to begin is to write out all the marketing channels you have, and bucket them into online or offline.

 

Pro tip: It is important to understand that offline channel logic in Bizible is actually determined by the Campaign object, specifically (when using Salesforce), the Salesforce Campaign Type - that is how all the touchpoints are mapped into channels and subchannels. So be sure to have all of your “SFDC Campaign Type” ducks in a row before you implement!

 

Sounds pretty simple, right? There are always some curveballs in this game, so let’s look into just a few examples of challenges you might face.

 

Scenario 1:
We did a third party webinar, where all the leads registered for the webinar on the third party website—but this was a paid webinar where we received the list of leads after the webinar. How can we track the ROI here?

 

Tricky. Since it’s a website, you immediately think “online!” which is true - however, since the registrations were done on a third party website where the registrants were not tracked with the Bizible javascript code, the registration touchpoint won’t show up for you. For this instance, I suggest using a campaign in your CRM where you can enable touchpoints, adding all the leads you acquired for that third party webinar to this campaign. By adding all these leads to the campaign with touchpoints enabled, they will all get a touchpoint for that particular campaign/third party webinar.

Pro tip: You can update the touchpoint date inside the SFDC campaign to be the date of the webinar, to be most accurate

 

Scenario 2:
We sent out a direct mail postcard, asking people to visit a webpage to sign up for a demo at a tradeshow. We’d like to track the ROI of that postcard, what’s the best way to do this?

 

For instances like this it may be a personal preference, and may also depend on the tools you have in place. If you sent the postcard out with a vanity URL such as “https://www.mywebsite.com/meetus” you can use data from something such as Google Analytics to track page visits, and since there is a form on this page, you may also be able to track the form fills on that particular page, or from that referrer if the vanity URL initiates a redirect to a page that exists. Personally, I want to be able to track the conversions in an SFDC campaign where I can also input the costs to send the postcard out. So I would enter all the people we sent the invite to as “invited” (or sent, or whatever status you have!) and then I would have a trigger campaign in Marketo (or any other tool you use) that would change their status in that campaign to “registered” if they fill out the form. This way, we can see the ROI in the campaign because we are only adding touchpoints to “responded” members, or “registrants”. Of course, you can put referrer parameter criteria in place so that you are only counting those that filled out the form that came from the postcard.

 

So there you have it. Online vs. Offline channels are not too terribly hard once you think it through, but it is certainly helpful to sit down and make sure you know which of your channels play in each space before you get started.

Hello Marketing Nation,

 

It's that time of the year! Marketo is now inviting all Marketo Certified Experts to apply to become a 2020 Marketo Champion. Marketo Champions are an essential part of our advocate community, often providing content for all ranges of expertise. Whether you've seen some of their content here on Community or you've seen them speak at one of our global events, the Champions are deeply devoted to helping our customers achieve their goals, both personally and professionally, with Marketo. The application will be open until November 29th.

 

For more details around becoming a Marketo Champion, check out the Champion Info page listed here: Requirements & Benefits of the Champion Program 

 

If you're ready to apply today, you can find the application here: 2020 Marketo Champion Application 

 

If you have any further questions about the Champion Program’s criteria & requirements, please email us at customermarketing@marketo.com

 

Good luck to all the 2020 applicants!

For arithmetic in Velocity, the only safe approach is the so-called “tools-ish” one: use MathTool methods like $math.add, $math.sub, and  $math.mul.

 

The rule

Aside from the one exception later in this post, you should not use the Java-like math operators (+ and -).

 

The core reason is the plus sign + and minus sign - have different syntax rules in Velocity! You can easily create fatal errors – or worse, lines that are silently ignored – by forgetting that - must be surrounded by spaces.

 

That’s right, only one of these is correct Velocity syntax for subtraction:

 

#set( $a = 99 )
#set( $b = $a - 1 ) ## correct
#set( $b = $a -1 ) ## fatal ParserException!
#set( $b = $a- 1 ) ## fatal ParserException!
#set( $b = $a-1 ) ## non-fatal, but doesn’t subtract!!!

 

So I always teach people to use #set( $b = $math.sub($a,1) ). It’s longer, but you’ll never break your code by switching sub and add.

 

The exception

Despite the above, in the course of developing my Base64 encoding in Velocity code, I discovered a unique requirement that could only be met by purposely using the + operator instead of $math.add.

 

This is the only exception I’ve found, and it doesn’t override my recommendation above. But it is – to me and I hope to the couple of people like me out there – fascinating.

 

So.

 

In Base64 encoding, one of the steps requires Integers to be converted to binary Strings, 42 → “00101010”. (If this is complete gibberish to you, you can reread this post when you have a couple more years of development under your belt!)

 

To get those Integers, you need to convert signed Bytes (-128 through 127) to their equivalent all-positive Integers (0 through 255).

 

To convert signed Bytes, you need to use a bitwise AND. But the traditional AND operator, the & symbol in Java and most other languages, isn’t available in Velocity.

 

Since you can’t use notation like #set( $c = $a & $b ) in Velocity (that’s a fatal parser error) you need to find a method-based way – a “tools-ish” way, somewhat ironically – to do a bitwise AND: something like #set( $c = $a.and($b) ).

 

Miraculously, such a method does exist (after you do some hunting) so long as $a is a Java BigInteger.  

 

Hmm. So now you need to be able to construct a BigInteger from an Integer.

 
Getting a BigInteger

A BigInteger isn’t something you can explicitly create in Marketo’s Velocity (post-June 2019, that is), only implicitly. There’s one way to cheat it, and that is to set a variable to just outside of the range of a Long. If you know off the type of your head that the max positive Long is 9,223,372,036,854,775,807 (~9 quintillion) then you can add 1 to that:
    

#set( $aBigInteger = 9223372036854775808 )
${aBigInteger.class} ## will be class java.math.BigInteger

 

 

Skipping the magic

But let’s say you don’t know that magic number – you only know that Java’s

concrete integer Number types  are, in order of increasing width, Byte → Short → Integer → Long → BigInteger. (I think it’s reasonable for a developer to know that progression, but not reasonable to expect someone to remember that 9,223,372,036,854,775,807 is special.)

 

If you didn’t know the magic Long-BigInteger boundary, how would you make sure Velocity allocated a type big enough for a BigInteger? You’d want to say:

 

Gimme a Number that can hold Long.MAX_VALUE + 1.

 

Of course to do that, you‘d have to get a handle on a Long (in order to refer to the constant Long.MAX_VALUE). Since Velocity doesn’t give you a Long unless you’ve asked for something bigger than an Integer, you have to say:

 

First, gimme a Number with the value 0. I know that’s an Integer, since that’s the default type for whole numbers in Velocity.


Then, gimme another Number that can hold ThisMustBeAnInteger.MAX_VALUE + 1. That’s gonna be a Long.

 

Finally, gimme yet another Number that fits ThisMustBeALong.MAX_VALUE + 1. That third number must be a BigInteger.

 

And here is where + acts differently from $math.add.

 

MathUtils vs. MathTools

Hidden in the doc for Velocity’s MathUtils (part of Velocity’s internal API, not for public consumption) you’ll find this gem:

 

So now we know Velocity can do – in fact is explicitly documented to do – exactly what we need. If we add 1 to the max value of a Long, the result will be a BigInteger (and will also have the correct value, obviously).

 

Except this only applies to the literal + operator. MathTools’ $math.add also does addition, but doesn’t implement the “overflow correction” logic.

 

Let’s compare.

 

Using the + operator:

#set( $Integer = 0 )
Integer $Integer.class.getName() = $Integer
#set( $Long = $field.in($Integer).MAX_VALUE + 1 )
Long $Long.class.getName() = $Long
#set( $BigInteger = $field.in($Long).MAX_VALUE + 1 )
BigInteger $BigInteger.class.getName() = $BigInteger

 

The output will be:

Integer java.lang.Integer = 0
Long java.lang.Long = 2147483648
BigInteger java.math.BigInteger = 9223372036854775808


Exactly what we need. $BigInteger is a BigInteger we can use as a factory to make more BigIntegers ($BigInteger.valueOf) and then do fancy stuff like .and() and .or() and  .xor() which would otherwise be impossible in Velocity.

 

Let’s try using $math.add:

#set( $Integer = 0 )
Integer $Integer.class.getName() = $Integer
#set( $Long = $math.add($field.in($Integer).MAX_VALUE,1) )
Long $Long.class.getName() = $Long
#set( $BigInteger = $math.add($field.in($Long).MAX_VALUE,1) )
BigInteger $BigInteger.class.getName() = $BigInteger

 

 

The output will be:

Integer java.lang.Integer = 0
Long java.lang.Long = 2147483648
BigInteger java.lang.Long = 9223372036854775807

 

 

Whoa. We attempted to add 1 to the largest Long, but the attempt silently failed. $BigInteger (despite the name) remains a Long, and it never gets incremented: it’s still Long.MAX_VALUE or 9,223,372,036,854,775,807.

 

It’s notable, but doesn’t change the rule

The likelihood that you’d otherwise need to perform BigInteger-range arithmetic within Marketo is basically zero.

 

I mean, it would have to be something like… you’re using a webhook to take the squares of lead scores, and you have a score in the millions and must raise it to the power of 2 in a Velocity token. Let’s face it, relative to the chances you will at some point forget the spaces around -, that’s just not gonna happen.  The risk/reward overwhelmingly favors $math.add and $math.sub.

 

In this case we needed a BigInteger not because of its infinite range of values, but because we need to get at the cool methods it has – which Integers and Longs do not. (The underlying value is tiny, between -128 and 127 then converted to between 0 and 255.) So for this particular purpose, deliberately using + makes sense.

The removal of Reflection-based features from Marketo’s Velocity environment in June 2019 was a disappointment — however inevitable — to those of us who push email scripting to the limit.

 

Requirements like encoding, hashing, encryption, flexible rounding and randomization, XPath queries, and advanced filters/sorts on Custom Objects could no longer be met with Marketo alone. (Webhooks can fulfill a subset of those needs, but with nowhere near the performance and simplicity of Velocity.)

 

At the prodding of Community user TM, I set out to see if Base64 encoding, one of the Java methods that used to be callable from Velocity, could be written with “pure” Velocity and public Java methods, without relying on the Reflection API.

 

I was indeed able to do it... though unlike most of my Velocity work, this one wouldn’t File Under Fun!

 

You can call the #userlandBase64_v3 macro below in 4 ways:

 

#1: Basic usage

#userlandBase64_v3("some string")

The result uses the standard RFC4648 Base64 character set (A-Za-z0-9+/). (That is, the characters used/expected by browsers’ native btoa()/atob(), not a “URL-safe” variant.)

 

#2: Automatic URL encoding

#userlandBase64_v3("some string",true)

This option URL-encodes the input string (using $esc.url) before Base64-ing it, which is critical if (a) you have non-Latin-1 characters in the string and (b) you want to use the browser’s native atob() to decode the result.[1]

 

#3: Basic + URL-safe characters

#userlandBase64_v3("some string",false,"RFC4648URLSafePadding")

This result is always safe to embed in URLs directly, even in the path, because it uses - and _ in place of the more sensitive + and /.

 

#4: URL encoding + URL-safe characters: The works

#userlandBase64_v3("some string",true,"RFC4648URLSafePadding")

The safest route, as it accounts for both non-Latin-1 in the input string and for possible URL conflicts.[2]

 

Get the code

I don’t want to digress into how the code works in this post, but perhaps there’ll be a Code Anatomy follow up... my version of the code has lots of comments!

 

Enjoy...

 

#**
* Base64 encoding in Velocity without Reflection
* @version v3 2010-10-11
* @author Sanford Whiteman, TEKNKL
* @license MIT
* @param $inputString String String to transform
* @param $alsoURIEncode Boolean [default false] URI-encode the String first (support browser-native atob())
* @param $charset String ["RFC4648" | "RFC4648URLSafePadding" | default "RFC4648"] 64-character set
*#
#macro( userlandBase64_v3 $inputString $alsoURIEncode $charset)
#set( $Base64PaddingChar = "=" )
#set( $Base64ByteChunkSize = 3 )
#set( $Base64BitGroupSize = 6 )
#set( $Base64Charsets = {
  "RFC4648" : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
  "RFC4648URLSafePadding" : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
} )
#set( $Base64Charset = $display.alt($Base64Charsets[$charset],$Base64Charsets["RFC4648"]) )
#set( $uriEncode = $display.alt($alsoURIEncode, false) )
#set( $Integer = 0 )
#set( $Long = $field.in($Integer).MAX_VALUE + 1 )
#set( $BigInteger = $field.in($Long).MAX_VALUE + 1 )
#set( $String = "" )
#set( $Base64Final = [] )
#if( $uriEncode )
#set( $inputString = $esc.url($inputString) )
#end
#set( $inputBytes = $inputString.getBytes() )
#set( $inputList = [] )
#set( $void = $inputList.addAll( $inputBytes.subList(0,$inputBytes.size())) )
#set( $inputSizeB = $inputList.size() )
#set( $padding =  $math.mod($inputSizeB, $Base64ByteChunkSize) )
#if( $padding > 0 )
#foreach( $pad in [1..$math.sub($Base64ByteChunkSize,$padding)] )
#set( $void = $inputList.add($null) )
#end
#end
#foreach( $byteGroup in [0..$math.sub($math.idiv($inputList.size(),$Base64ByteChunkSize),1)])
#set( $startOffset = $math.mul($byteGroup,$Base64ByteChunkSize) )
#set( $endOffset = $math.add($startOffset,$Base64ByteChunkSize) )
#set( $bytes = $inputList.subList($startOffset,$endOffset) )
#set( $integerList = [] )
#foreach( $byte in $bytes )
#if( $byte )
#set( $currentInteger = $convert.toInteger($byte) )
#set( $void = $integerList.add($convert.toInteger($BigInteger.valueOf($currentInteger).and($BigInteger.valueOf(255))) ))
#else
#set( $void = $integerList.add($null) )
#end
#end
#set( $binStrings = [] )
#foreach( $currentInteger in $integerList )
#set( $void = $binStrings.add($String.format("%8s",$Integer.toBinaryString($currentInteger)).replace(" ","0")) )
#end
#set( $allBinStrings = $display.list($binStrings,"") )
#set( $bitGroups = [] )
#foreach( $bitGroup in [0..$math.sub($math.idiv($allBinStrings.length(),$Base64BitGroupSize),1)] )
#set( $startOffset = $math.mul($bitGroup,$Base64BitGroupSize) )
#set( $endOffset = $math.add($startOffset,$Base64BitGroupSize) )
#set( $void = $bitGroups.add($allBinStrings.substring($startOffset,$endOffset)) )
#end
#foreach( $group in $bitGroups )
#if( !$group.contains("null") )
#set( $Base64CharsetPos = $Integer.parseInt($group,2) )
#set( $Base64Char = $Base64Charset.charAt($Base64CharsetPos) )
#else
#set( $Base64Char = $Base64PaddingChar )
#end
#set( $void = $Base64Final.add($Base64Char) )
#end
#end
$display.list($Base64Final,"")
#end

 

 


Notes

[1] Related browser-side decoding:

decodeURIComponent(
  atob(myEncodedString).replace("+"," ")
);

 

[2] Browser-side hint for this format:

decodeURIComponent(
  atob(
    myEncodedString.replace("-","+").replace("_","/")
  ).replace("+"," ")
);

 

Filter Blog

By date: By tag: