SOLVED

Velocity question. Resort to default when missing values in JSON field

Go to solution
Highlighted
Level 1

Velocity question. Resort to default when missing values in JSON field

I'm having an issue where instead of Marketo showing the default value when previewing an email, it shows ${drivername}. In the campaign I'm building, not all values set in velocity will exist in the JSON field. Some records may have a few while another has all.

How do I code my velocity so that if a value I'm setting is missing from the JSON field, it is overlooked and the echo script I set in a token doesn't output ${drivername}, but outputs the default value "there", such as {{my.drivername:DEFAULT=there}}?

##set defaults
#set( $utm_source = "mkto-email" )
#set( $utm_medium = "customer-engagement" )
#set( $utm_campaign = "TXreferralsprogram" )
#set( $messageHTML = "" )
##check JSON and set vars
#if( $lead.carpoolJSON.isEmpty() )
#set( $lead.carpoolJSON = '[]' )
#elseif( $lead.carpoolJSON )
#set( $refData = '#set( $refData = ' + ${lead.carpoolJSON} + ' )' )
#evaluate($refData)
#end
#foreach( $ref in $refData )
#set( $referralURL = $ref[0].driverReferralUrl )
#set( $driveruniqueUrl = $ref[0].driverUniqueUrl )
#set( $custommessage = $ref[0].customMessage )
#set( $drivername = $ref[0].driverName )
#set( $driverdashboardurl = $ref[0].driverDashboardUrl )
#set( $giftSelectionUrl = $ref[0].giftSelectionUrl )
#set( $passengername = $ref[0].passengerName )
#end
#if( $custommessage.isEmpty() )
#set( $custommessage = '[]' )
#elseif( $custommessage )
#set( $messageHTML = '<td align="center" vertical-align="middle" style="font-size:24px; font-style: italic; font-weight: 400; color: #184c6d; padding:15px;word-break:break-word;"><table cellpadding="15" cellspacing="0" border="0" role="presentation" style="background: #f8f8f8; max-width: 520px; width: 100%;"><tr><td>&quot;${custommessage}&quot; </br></br><span style="font-weight: 700; font-size: 18px;">${drivername}</span></br></br></td></tr></table></td>' )
#end
#set( $referralURL2 = $referralURL.replace("https://", "") )
#set( $driveruniqueUrl2 = $driveruniqueUrl.replace("https://", "") )
#set( $driverdashboardurl2 = $driverdashboardurl.replace("https://", "") )
#set( $giftSelectionUrl2 = $giftSelectionUrl.replace("https://", "") )
Tags (1)
1 ACCEPTED SOLUTION

Accepted Solutions
Highlighted
Level 10 - Community Moderator

Re: Velocity question. Resort to default when missing values in JSON field

You haven't really detailed your data model, so I've had to infer it from your code (which is always risky).

Assuming all properties are present, your code may expect a JSON structure like this, an array with an inner array with inner object(s):

[
[
{
"driverReferralUrl" : "https://example1.com",
"driverUniqueUrl" : "https://example2.com",
"customMessage" : "Hello",
"driverName" : "Raj",
"driverDashboardUrl" : "https://example3.com",
"giftSelectionUrl" : "https://example4.com",
"passengerName" : "Sandy"
}
]
]‍‍‍‍‍‍‍‍‍‍‍‍‍

However, it loops over the outer array, without exiting, and reads the first object of the inner array. So in fact JSON like this (shortened for readability) will still be parsed:

[
[
{
"driverName" : "Raj",
"passengerName" : "Sandy"
},
{
"driverName" : "Raj 2",
"passengerName" : "Sandy 2"
}
],
[
{
"driverName" : "Raj 3",
"passengerName" : "Sandy 3"
},
{
"driverName" : "Raj 4",
"passengerName" : "Sandy 4"
}
]
]‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Leaving you with with driverName "Raj 3" and passengerName "Sandy 3".

I don't know if this result is expected or desired. But any time you're repeatedly overwriting a global value in a loop, and seeking the same object multiple times, that's generally a sign that something is wrong, and certainly inefficient.  If you only care about the 1st object in the 1st array, you should go right to it with $refData[0][0], don't iterate needlessly.

Also not sure what you really expect to happen if the JSON field is completely empty. Even though you're transforming it from an empty string into "[]" (which is a really good idea), because everything you have is inside a loop over the (empty) outer array, none of your output variables are set in that case.

More notes:

  • The standard way to #evaluate VTL uses escapes. Some eval methods work in Marketo's Velocity but not in a vanilla Velocity install. You should work with the more standard way (as you'll see in my code below). Keep aware that Velocity doesn't actually support all valid JSON. It's a (very happy) coincidence that VTL Map, List, String, and Number literals look almost exactly like JavaScript Object, Array, String, and Number literals, but there are still pitfalls.  Look out for my upcoming blog post on "untreated" JSON -- that is, JSON that hasn't been generated with Velocity in mind.
  • When working with URLs, remember that some sequences may appear (unencoded) in different parts of a URL. Countless code snippets out there (mostly in JS) are broken because they aren't based on the URL RFC -- even if you're "sure" your URLs look a certain way, parse them per the standard.  If you want to strip off the protocol, you must anchor your search to the beginning of the URL, i.e. using a regex.
  • Single quotes and double quotes behave differently in Velocity, variables inside single quotes are not substituted
  • As for the default logic, what you have will work, but I don't like to rely on the accessing-null-property-is-a-noop behavior. Instead, set up a default data block and merge into those defaults. That is, set your default values (so no property is ever missing) and then putAll the live values on top of those.

So I recommend this code over what you've got, but you have to think about the above business logic questions as well.

## UTM params
#set( $utm_source = "mkto-email" )
#set( $utm_medium = "customer-engagement" )
#set( $utm_campaign = "TXreferralsprogram" )
## JSON defaults for missing properties
#set( $defaults = {
"driverReferralUrl" : "",
"driverUniqueUrl" : "",
"customMessage" : "",
"driverName" : "there",
"driverDashboardUrl" : "",
"giftSelectionUrl" : "",
"passengerName" : "Your referred friend"
})
## ensure JSON is parsed as at least an empty List
#if( $lead.carpoolJSON.isEmpty() )
#set( $lead.carpoolJSON = '[]' )
#end
## hydrate JSON
#set( $refData = "${esc.h}set( ${esc.d}refData = ${lead.carpoolJSON} )" )
#evaluate($refData)
## start with defaults
#set( $focusedRef = $defaults.clone() )
#foreach( $ref in $refData )
#set( $void = $focusedRef.putAll($ref[0]) )
#set( $referralURL = $focusedRef.driverReferralUrl )
#set( $driveruniqueUrl = $focusedRef.driverUniqueUrl )
#set( $custommessage = $focusedRef.customMessage )
#set( $drivername = $focusedRef.driverName )
#set( $driverdashboardurl = $focusedRef.driverDashboardUrl )
#set( $giftSelectionUrl = $focusedRef.giftSelectionUrl )
#set( $passengername = $focusedRef.passengerName )
#end
#if( !$custommessage.isEmpty() )
#define( $messageHTML )
<td align="center" vertical-align="middle" style="font-size:24px; font-style: italic; font-weight: 400; color: #184c6d; padding:15px;word-break:break-word;"><table cellpadding="15" cellspacing="0" border="0" role="presentation" style="background: #f8f8f8; max-width: 520px; width: 100%;"><tr><td>&quot;${custommessage}&quot; </br></br><span style="font-weight: 700; font-size: 18px;">${drivername}</span></br></br></td></tr></table></td>
#end
#else
#define( $messageHTML )
[]
#end
#end
#set( $referralURLNoHost = $referralURL.replaceAll("^https://", "") )
#set( $driveruniqueUrlNoHost = $driveruniqueUrl.replaceAll("^https://", "") )
#set( $driverdashboardurlNoHost = $driverdashboardurl.replaceAll("^https://", "") )
#set( $giftSelectionUrlNoHost = $giftSelectionUrl.replaceAll("^https://", "") )‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

View solution in original post

7 REPLIES 7
Highlighted
Level 10 - Community Moderator

Re: Velocity question. Resort to default when missing values in JSON field

Please highlight your code (as Java, closest to VTL) using the syntax highlighter, then we'll continue.

Highlighted
Level 1

Re: Velocity question. Resort to default when missing values in JSON field

Hey Sanford. Thanks. I edited my question and highlighted the code as Java.

Highlighted
Level 10 - Community Moderator

Re: Velocity question. Resort to default when missing values in JSON field

Thanks, I'll answer when back at the office in ~1.5h, wanted it to be ready for me.

Highlighted
Level 1

Re: Velocity question. Resort to default when missing values in JSON field

I believe I fixed it. I set my defaults at the top of the script. Seems to be working. There are some instances where the values will be passed into the JSON field with "". So that's why I did line 29-31. Thank you though Sanford. If you have the time to evaluate my coding, I'd appreciate that. I'm new to Velocity.

##set defaults
#set( $utm_source = "mkto-email" )
#set( $utm_medium = "customer-engagement" )
#set( $utm_campaign = "TXreferralsprogram" )
#set( $messageHTML = "" )
#set( $drivername = "there" )
#set( $passengername = "your referred friend" )
##check JSON and set vars
#if( $lead.carpoolJSON.isEmpty() )
#set( $lead.carpoolJSON = '[]' )
#elseif( $lead.carpoolJSON )
#set( $refData = '#set( $refData = ' + ${lead.carpoolJSON} + ' )' )
#evaluate($refData)
#end
#foreach( $ref in $refData )
#set( $referralURL = $ref[0].driverReferralUrl )
#set( $driveruniqueUrl = $ref[0].driverUniqueUrl )
#set( $custommessage = $ref[0].customMessage )
#set( $drivername = $ref[0].driverName )
#set( $driverdashboardurl = $ref[0].driverDashboardUrl )
#set( $giftSelectionUrl = $ref[0].giftSelectionUrl )
#set( $passengername = $ref[0].passengerName )
#end
#if( $custommessage.isEmpty() )
#set( $custommessage = '[]' )
#elseif( $custommessage )
#set( $messageHTML = '<td align="center" vertical-align="middle" style="font-size:24px; font-style: italic; font-weight: 400; color: #184c6d; padding:15px;word-break:break-word;"><table cellpadding="15" cellspacing="0" border="0" role="presentation" style="background: #f8f8f8; max-width: 520px; width: 100%;"><tr><td>&quot;${custommessage}&quot; </br></br><span style="font-weight: 700; font-size: 18px;">${drivername}</span></br></br></td></tr></table></td>' )
#end
#if( $passengername.isEmpty() )
#set( $passengername = 'Your referred friend' )
#end
#set( $referralURL2 = $referralURL.replace("https://", "") )
#set( $driveruniqueUrl2 = $driveruniqueUrl.replace("https://", "") )
#set( $driverdashboardurl2 = $driverdashboardurl.replace("https://", "") )
#set( $giftSelectionUrl2 = $giftSelectionUrl.replace("https://", "") )
Highlighted
Level 10 - Community Moderator

Re: Velocity question. Resort to default when missing values in JSON field

You haven't really detailed your data model, so I've had to infer it from your code (which is always risky).

Assuming all properties are present, your code may expect a JSON structure like this, an array with an inner array with inner object(s):

[
[
{
"driverReferralUrl" : "https://example1.com",
"driverUniqueUrl" : "https://example2.com",
"customMessage" : "Hello",
"driverName" : "Raj",
"driverDashboardUrl" : "https://example3.com",
"giftSelectionUrl" : "https://example4.com",
"passengerName" : "Sandy"
}
]
]‍‍‍‍‍‍‍‍‍‍‍‍‍

However, it loops over the outer array, without exiting, and reads the first object of the inner array. So in fact JSON like this (shortened for readability) will still be parsed:

[
[
{
"driverName" : "Raj",
"passengerName" : "Sandy"
},
{
"driverName" : "Raj 2",
"passengerName" : "Sandy 2"
}
],
[
{
"driverName" : "Raj 3",
"passengerName" : "Sandy 3"
},
{
"driverName" : "Raj 4",
"passengerName" : "Sandy 4"
}
]
]‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Leaving you with with driverName "Raj 3" and passengerName "Sandy 3".

I don't know if this result is expected or desired. But any time you're repeatedly overwriting a global value in a loop, and seeking the same object multiple times, that's generally a sign that something is wrong, and certainly inefficient.  If you only care about the 1st object in the 1st array, you should go right to it with $refData[0][0], don't iterate needlessly.

Also not sure what you really expect to happen if the JSON field is completely empty. Even though you're transforming it from an empty string into "[]" (which is a really good idea), because everything you have is inside a loop over the (empty) outer array, none of your output variables are set in that case.

More notes:

  • The standard way to #evaluate VTL uses escapes. Some eval methods work in Marketo's Velocity but not in a vanilla Velocity install. You should work with the more standard way (as you'll see in my code below). Keep aware that Velocity doesn't actually support all valid JSON. It's a (very happy) coincidence that VTL Map, List, String, and Number literals look almost exactly like JavaScript Object, Array, String, and Number literals, but there are still pitfalls.  Look out for my upcoming blog post on "untreated" JSON -- that is, JSON that hasn't been generated with Velocity in mind.
  • When working with URLs, remember that some sequences may appear (unencoded) in different parts of a URL. Countless code snippets out there (mostly in JS) are broken because they aren't based on the URL RFC -- even if you're "sure" your URLs look a certain way, parse them per the standard.  If you want to strip off the protocol, you must anchor your search to the beginning of the URL, i.e. using a regex.
  • Single quotes and double quotes behave differently in Velocity, variables inside single quotes are not substituted
  • As for the default logic, what you have will work, but I don't like to rely on the accessing-null-property-is-a-noop behavior. Instead, set up a default data block and merge into those defaults. That is, set your default values (so no property is ever missing) and then putAll the live values on top of those.

So I recommend this code over what you've got, but you have to think about the above business logic questions as well.

## UTM params
#set( $utm_source = "mkto-email" )
#set( $utm_medium = "customer-engagement" )
#set( $utm_campaign = "TXreferralsprogram" )
## JSON defaults for missing properties
#set( $defaults = {
"driverReferralUrl" : "",
"driverUniqueUrl" : "",
"customMessage" : "",
"driverName" : "there",
"driverDashboardUrl" : "",
"giftSelectionUrl" : "",
"passengerName" : "Your referred friend"
})
## ensure JSON is parsed as at least an empty List
#if( $lead.carpoolJSON.isEmpty() )
#set( $lead.carpoolJSON = '[]' )
#end
## hydrate JSON
#set( $refData = "${esc.h}set( ${esc.d}refData = ${lead.carpoolJSON} )" )
#evaluate($refData)
## start with defaults
#set( $focusedRef = $defaults.clone() )
#foreach( $ref in $refData )
#set( $void = $focusedRef.putAll($ref[0]) )
#set( $referralURL = $focusedRef.driverReferralUrl )
#set( $driveruniqueUrl = $focusedRef.driverUniqueUrl )
#set( $custommessage = $focusedRef.customMessage )
#set( $drivername = $focusedRef.driverName )
#set( $driverdashboardurl = $focusedRef.driverDashboardUrl )
#set( $giftSelectionUrl = $focusedRef.giftSelectionUrl )
#set( $passengername = $focusedRef.passengerName )
#end
#if( !$custommessage.isEmpty() )
#define( $messageHTML )
<td align="center" vertical-align="middle" style="font-size:24px; font-style: italic; font-weight: 400; color: #184c6d; padding:15px;word-break:break-word;"><table cellpadding="15" cellspacing="0" border="0" role="presentation" style="background: #f8f8f8; max-width: 520px; width: 100%;"><tr><td>&quot;${custommessage}&quot; </br></br><span style="font-weight: 700; font-size: 18px;">${drivername}</span></br></br></td></tr></table></td>
#end
#else
#define( $messageHTML )
[]
#end
#end
#set( $referralURLNoHost = $referralURL.replaceAll("^https://", "") )
#set( $driveruniqueUrlNoHost = $driveruniqueUrl.replaceAll("^https://", "") )
#set( $driverdashboardurlNoHost = $driverdashboardurl.replaceAll("^https://", "") )
#set( $giftSelectionUrlNoHost = $giftSelectionUrl.replaceAll("^https://", "") )‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

View solution in original post

Highlighted
Level 1

Re: Velocity question. Resort to default when missing values in JSON field

Sanford, you are the greatest. I will take your advice and suggestions in consideration. I'm still learning Velocity, so this made me happy to see a very thorough reply.

Highlighted
Level 10 - Community Moderator

Re: Velocity question. Resort to default when missing values in JSON field

Make sure you're following my blog posts, lots out there and more coming up.