For multivalued Marketo fields, consider URL-encoding instead of JSON or semicolons
Storing JSON in a Marketo Textarea field (or String, if it’s short enough) is a cool way to add new dimensions to a Marketo person record.
JSON fields are easy-to-write alternatives to Marketo Custom Objects — “poor man’s custom objects,” as I’ve called them before. They can be parsed into one-to-many lists of objects using Velocity (in emails) or JavaScript (on landing pages).
But you know what? While I fully endorse JSON fields, and was the first user to popularize ’em, they’re not always the right fit. If you need a set of key-value pairs (just one new dimension, you could say, not multiple nested dimensions) consider a URL-encoded string, a.k.a. query string without the leading ?
instead of JSON.
Why? Well, primarily because Velocity has a built-in decoder for query strings. It doesn’t have a built-in decoder for JSON.
While you can easily get VTL to parse JSON — just make sure there aren’t any null
s and use full Unicode \uNNNN
escapes! — going down the JSON road may be unnecessarily complex. If you don’t need a list of objects and/or nested objects, go for URL-encoding.
What brought this up
The other day, I started work on a system for storing a lead’s “content class preferences” in a field. That field will later be used to show/hide modules in the final email (using a new method that will blow your mind in a future post!). But to start, I was weighing different formats.
The instinctive way is a semicolon-delimited list:
But the client (s/o Jim!) pointed out this would be insufficient, since someone may opt-in, expressly opt-out, or have no preference set for a given class of content.
So my typical approach would be a JSON field with explicit Boolean values for each expressed preference:
But at this client, preferences will often be set via CSV uploads (in addition to their preference center). And as much as JSON is relatively easy to read and type, it feels more likely that they’ll mess up the JSON syntax than URL encoding.
Therefore, taking the requirements + risks into account, I went with:
Decoding a URL-encoded string in Velocity
You can parse that value easily with $link.uri
, prepending a question mark so it gets parsed as a query-string-relative URI:
#if( $lead.contentClassPreferences.isEmpty() )
#set( $contentClassPreferences = {} )
#else
#set( $contentClassPreferences = $link.uri("?${lead.contentClassPreferences}").getParams() )
#end
$contentClassPreferences
will then be a classic Map:
{
"Technical" : "false",
"Educational" : "true"
}
And you’d get the value of a single preference with:
$contentClassPreferences.get("Technical")
JSON still rules if you need nested objects and/or typed values
With URL encoding, every key and value is a String (this is not a Marketo or Velocity thing — it’s always true of query params, though people tend to forget it).
So the value of the preference "Technical"
above is "false"
(with quotes), not a Boolean false
. Make sure you check equality accordingly:
## like this
#if( $contentClassPreferences.get("Technical").equals("false") )
## not like this
#if( $contentClassPreferences.get("Technical").equals(false) )
In contrast, JSON is parsed into String, Boolean, and Number types[1], which may be better depending on your needs.
And as noted before, if you need a set of objects (like how a CO works) the only way is JSON:
[
{
"productName" : "T7470WHCL Hands Free Paper Towel Dispenser",
"purchaseDate" : "2022-01-15"
"warrantyEndDate" : "2023-02-01"
},
{
"productName" : "SHF900TBL Touchless Foam Soap Dispenser",
"purchaseDate" : "2021-03-20"
"warrantyEndDate" : "2024-03-20"
}
]
Notes
[1] Deep in the weeds here, but you could argue that the fact that JSON doesn’t support undefined
(ever) and Velocity doesn’t support JSON null
(ever) is a weakness, type-wise.
Both Technical=false&Educational=true&Events
and Technical=false&Educational=true&Events=
will set the preference "Events"
to null
. And that can be distinguished from a completely missing (i.e. undefined) preference:
#if( $contentClassPreferences.containsKey("Events") )
#set( $Events = $contentClassPreferences.get("Events") )
#if( $Events )
## $Events is present and is a String
#else
## $Events is present but is null, interpret that as you will
#end
#else
## $Events was missing completely
#end