IBM Cloud Docs
Expression language methods for dialog

Expression language methods for dialog

You can process values that are extracted from user utterances that you want to reference in a context variable, condition, or elsewhere in the response.

Where to use the expression syntax

To expand variable values inside other variables, or apply methods to output text or context variables, use the <? expression ?> expression syntax. For example:

  • Referencing a user's input from a dialog node text response

    You said <? input.text ?>.
    
  • Incrementing a numeric property from the JSON editor

    "output":{"number":"<? output.number + 1 ?>"}
    
  • Checking for a specific entity value from a dialog node condition

    @city.toLowerCase() == 'paris'
    
  • Checking for a specific date range from a dialog node response condition

    @sys-date.after(today())
    
  • Adding an element to a context variable array from the context editor

Context variable array
Context variable name Context variable value
toppings <? context.toppings.append( 'onions' ) ?>

You can use SpEL expressions in dialog node conditions and dialog node response conditions also.

When a SpEL expression is used in a node condition, the surrounding <? ?> syntax is not required.

The following sections describe methods that you can use to process values. They are organized by data type:

Arrays

You cannot use these methods to check for a value in an array in a node condition or response condition within the same node in which you set the array values.

JSONArray.addAll(JSONArray)

This method appends one array to another.

For this dialog runtime context:

{
  "context": {
    "toppings_array": ["onion", "olives"],
    "more_toppings": ["mushroom","pepperoni"]
  }
}

Make this update:

{
  "context": {
    "toppings_array": "<? $toppings_array.addAll($more_toppings) ?>"
  }
}

Result: The method itself returns null. However, the first array is updated to include the values from the second array.

{
  "context": {
    "toppings_array": ["onion", "olives", "mushroom", "pepperoni"]
  }
}

JSONArray.append(object)

This method appends a new value to the JSONArray and returns the modified JSONArray.

For this dialog runtime context:

{
  "context": {
    "toppings_array": ["onion", "olives"]
  }
}

Make this update:

{
  "context": {
    "toppings_array": "<? $toppings_array.append('ketchup', 'tomatoes') ?>"
  }
}

Result:

{
  "context": {
    "toppings_array": ["onion", "olives", "ketchup", "tomatoes"]
  }
}

JSONArray.clear()

This method clears all values from the array and returns null.

Use the following expression in the output to define a field that clears an array that you saved to a context variable ($toppings_array) of its values.

{
  "output": {
    "array_eraser": "<? $toppings_array.clear() ?>"
  }
}

Then, if you reference the $toppings_array context variable, it returns '[]' only.

JSONArray.contains(Object value)

This method returns true if the input JSONArray contains the input value.

For this dialog runtime context that is set by a previous node or external application:

{
  "context": {
    "toppings_array": ["onion", "olives", "ham"]
  }
}

Dialog node or response condition:

$toppings_array.contains('ham')

Result: true because the array contains the element ham.

JSONArray.containsIgnoreCase(Object value)

This method returns true if the input JSONArray contains the input value, regardless of whether the value is specified in uppercase or lowercase letters.

For this dialog runtime context that is set by a previous node or external application:

{
  "context": {
    "toppings_array": ["onion", "olives", "ham"]
  }
}

Dialog node or response condition:

$toppings_array.containsIgnoreCase('HAM')

Result: true because the array contains the element ham and the case is ignored.

JSONArray.containsIntent(String intent_name, Double min_score, [Integer top_n])

This method returns true if the intents JSONArray specifically contains the specified intent, and that intent has a confidence score that is equal to or higher than the specified minimum score. Optionally, you can specify a number to indicate that the intent must be included within that number of top elements in the array. The top_n parameter is ignored if you specify a negative number.

Returns false if the specified intent is not in the array, does not have a confidence score that is equal to or greater than the minimum confidence score, or the array index of the intent is lower than the specified index location.

The service automatically generates an intents array that lists the intents that the service detects in the input whenever user input is submitted. The array lists all intents that are detected by the service in order of highest confidence first.

You can use this method in a node condition to not only check for the presence of an intent, but to set a confidence score threshold that must be met before the node can be processed and its response returned.

For example, use the following expression in a node condition when you want to trigger the dialog node only when the following conditions are met:

  • The #General_Ending intent is present.
  • The confidence score of the #General_Ending intent is over 80%.
  • The #General_Ending intent is one of the top 2 intents in the intents array.
intents.containsIntent("General_Ending", 0.8, 2)

JSONArray.filter(temp, "temp.property operator comparison_value")

Filters an array by comparing each array element value to a value you specify. This method is similar to a collection projection. A collection projection returns a filtered array based on a name in an array element name-value pair. The filter method returns a filtered array based on a value in an array element name-value pair.

The filter expression consists of the following values:

  • temp: Name of a variable that is used temporarily as each array element is evaluated. For example, city.

  • property: Element property that you want to compare to the comparison_value. Specify the property as a property of the temporary variable that you name in the first parameter. Use the syntax: temp.property. For example, if latitude is a valid element name for a name-value pair in the array, specify the property as city.latitude.

  • operator: Operator to use to compare the property value to the comparison_value.

    Supported operators are:

    Supported filter operators
    Operator Description
    == Is equal to
    > Is greater than
    < Is less than
    >= Is greater than or equal to
    <= Is less than or equal to
    != Is not equal to
  • comparison_value: Value that you want to compare each array element property value against. To specify a value that can change depending on the user input, use a context variable or entity as the value. If you specify a value that can vary, add logic to guarantee that the comparison_value value is valid at evaluation time or an error occurs.

Filter example 1

For example, you can use the filter method to evaluate an array that contains a set of city names and their population numbers to return a smaller array that contains only cities with a population over 5 million.

The following $cities context variable contains an array of objects. Each object contains a name and population property.

[
   {
      "name":"Tokyo",
      "population":9273000
   },
   {
      "name":"Rome",
      "population":2868104
   },
   {
      "name":"Beijing",
      "population":20693000
   },
   {
      "name":"Paris",
      "population":2241346
   }
]

In the following example, the arbitrary temporary variable name is city. The SpEL expression filters the $cities array to include only cities with a population of over 5 million:

$cities.filter("city", "city.population > 5000000")

The expression returns the following filtered array:

[
   {
      "name":"Tokyo",
      "population":9273000
   },
   {
      "name":"Beijing",
      "population":20693000
   }
]

You can use a collection projection to create a new array that includes only the city names from the array that is returned by the filter method. You can then use the join method to display the two name element values from the array as a String, and separate the values with a comma and a space.

The cities with more than 5 million people include <?  T(String).join(", ",($cities.filter("city", "city.population > 5000000")).![name]) ?>.

The resulting response is: The cities with more than 5 million people include Tokyo, Beijing.

Filter example 2

The power of the filter method is that you do not need to hardcode the comparison_value value. In this example, the hardcoded value of 5000000 is replaced with a context variable instead.

In this example, the $population_min context variable contains the number 5000000. The arbitrary temporary variable name is city. The SpEL expression filters the $cities array to include only cities with a population of over 5 million:

$cities.filter("city", "city.population > $population_min")

The expression returns the following filtered array:

[
   {
      "name":"Tokyo",
      "population":9273000
   },
   {
      "name":"Beijing",
      "population":20693000
   }
]

When you compare number values, be sure to set the context variable that is involved in the comparison to a valid value before the filter method is triggered. Null can be a valid value if the array element you are comparing it against might contain it. For example, if the population name and value pair for Tokyo is "population":null, and the comparison expression is "city.population == $population_min", then null would be a valid value for the $population_min context variable.

You can use a dialog node response expression like this:

The cities with more than $population_min people include <?  T(String).join(", ",($cities.filter("city", "city.population > $population_min")).![name]) ?>.

The resulting response is: The cities with more than 5000000 people include Tokyo, Beijing.

Filter example 3

In this example, an entity name is used as the comparison_value. The user input is, What is the population of Tokyo?. The arbitrary temporary variable name is y. You created an entity that is named @city that recognizes city names, including Tokyo.

$cities.filter("y", "y.name == @city")

The expression returns the following array:

[
   {
      "name":"Tokyo",
      "population":9273000
   }
]

You can use a collection project to get an array with only the population element from the original array, and then use the get method to return the value of the population element.

The population of @city is: <? ($cities.filter("y", "y.name == @city").![population]).get(0) ?>.

The expression returns: The population of Tokyo is 9273000.

JSONArray.get(Integer)

This method returns the input index from the JSONArray.

For this dialog runtime context that is set by a previous node or external application:

{
  "context": {
    "name": "John",
    "nested": {
      "array": [ "one", "two" ]
    }
  }
}

Dialog node or response condition:

$nested.array.get(0).getAsString().contains('one')

Result: True because the nested array contains one as a value.

Response:

"output": {
  "generic" : [
    {
      "values": [
        {
        "text" : "The first item in the array is <?$nested.array.get(0)?>"
        }
      ],
      "response_type": "text",
      "selection_policy": "sequential"
    }
  ]
  }

JSONArray.getRandomItem()

This method returns a random item from the input JSONArray.

For this dialog runtime context:

{
  "context": {
    "toppings_array": ["onion", "olives", "ham"]
  }
}

Dialog node output:

{
  "output": {
  "generic" : [
    {
      "values": [
        {
    "text": "<? $toppings_array.getRandomItem() ?> is a great choice!"
        }
      ],
      "response_type": "text",
      "selection_policy": "sequential"
    }
  ]
  }
}

Result: "ham is a great choice!" or "onion is a great choice!" or "olives is a great choice!"

The resulting output text is randomly chosen.

JSONArray.indexOf(value)

This method returns the index number of the element in the array that matches the value you specify as a parameter or -1 if the value is not found in the array. The value can be a String ("School"), Integer(8), or Double (9.1). The value must be an exact match and is case-sensitive.

For example, the following context variables contain arrays:

{
  "context": {
    "array1": ["Mary","Lamb","School"],
    "array2": [8,9,10],
    "array3": [8.1,9.1,10.1]
  }
}

The following expressions can be used to determine the array index at which the value is specified:

<? $array1.indexOf("Mary") ?> returns `0`
<? $array2.indexOf(9) ?> returns `1`
<? $array3.indexOf(10.1) ?> returns `2`

This method can be useful for getting the index of an element in an intents array, for example. You can apply the indexOf method to the array of intents generated each time user input is evaluated to determine the array index number of a specific intent.

intents.indexOf("General_Greetings")

If you want to know the confidence score for a specific intent, you can pass the earlier expression in as the index value to an expression with the syntax intents[index].confidence. For example:

intents[intents.indexOf("General_Greetings")].confidence

JSONArray.join(String delimiter)

This method joins all values in this array to a string. Values are converted to strings and delimited by the input delimiter.

For this dialog runtime context:

{
  "context": {
    "toppings_array": ["onion", "olives", "ham"]
  }
}

Dialog node output:

{
  "output": {
  "generic" : [
    {
      "values": [
        {
    "text": "This is the array: <? $toppings_array.join(';') ?>"
        }
      ],
      "response_type": "text",
      "selection_policy": "sequential"
    }
  ]
  }
}

Result:

This is the array: onion;olives;ham;

If a user input mentions multiple toppings, and you defined an entity that is named @toppings that can recognize topping mentions, you might use the following expression in the response to list the toppings that were mentioned:

So, you'd like <? @toppings.values.join(',') ?>.

If you define a variable that stores multiple values in a JSON array, you can return a subset of values from the array. Use the join() method to format them properly.

Collection projection

A collection projection SpEL expression extracts a subcollection from an array that contains objects. The syntax for a collection projection is array_that_contains_value_sets.![value_of_interest].

For example, the following context variable defines a JSON array that stores flight information. Each flight has two data points, the time and flight code.

"flights_found": [
  {
    "time": "10:00",
    "flight_code": "OK123"
  },
  {
    "time": "12:30",
    "flight_code": "LH421"
  },
  {
    "time": "16:15",
    "flight_code": "TS4156"
  }
]

To return the flight codes only, you can create a collection projection expression by using the following syntax:

<? $flights_found.![flight_code] ?>

This expression returns an array of the flight_code values as ["OK123","LH421","TS4156"]. See the SpEL Collection projection documentation for more details.

If you apply the join() method to the values in the returned array, the flight codes are displayed in a comma-separated list. For example, you can use the following syntax in a response:

The flights that fit your criteria are:
  <? T(String).join(",", $flights_found.![flight_code]) ?>.

Result: The flights that match your criteria are: OK123,LH421,TS4156.

JSONArray.joinToArray(template, retainDataType)

This method extracts information from each item in the array and builds a new array that is formatted according to the template you specify. The template can be a string, a JSON object, or an array. The method returns an array of strings, an array of objects, or an array of arrays, depending on the type of template.

This method is useful for formatting information as a string that you can return as part of the output of a dialog node, or for transforming data into a different structure so you can use it with an external API.

In the template, you can reference values from the source array by using the following syntax, where {property} represents the name of the property in the source array.

%e.{property}%

For example, suppose that your assistant stores an array that contains flight details in a context variable. The stored data might look like this:

"flights": [
      {
        "flight": "AZ1040",
        "origin": "JFK",
        "carrier": "Alitalia",
        "duration": 485,
        "destination": "FCO",
        "arrival_date": "2019-02-03",
        "arrival_time": "07:00",
        "departure_date": "2019-02-02",
        "departure_time": "16:45"
      },
      {
        "flight": "DL1710",
        "origin": "JFK",
        "carrier": "Delta",
        "duration": 379,
        "destination": "LAX",
        "arrival_date": "2019-02-02",
        "arrival_time": "10:19",
        "departure_date": "2019-02-02",
        "departure_time": "07:00"
      },
      {
        "flight": "VS4379",
        "origin": "BOS",
        "carrier": "Virgin Atlantic",
        "duration": 385,
        "destination": "LHR",
        "arrival_date": "2019-02-03",
        "arrival_time": "09:05",
        "departure_date": "2019-02-02",
        "departure_time": "21:40"
      }
    ]

To build an array of strings that describe these flights in a user-readable form, you might use the following expression:

${Flight_data}.joinToArray("Flight %e.flight% to %e.destination%", true)

This expression would return the following array of strings: ["Flight AZ1040 to FCO","Flight DL1710 to LAX","Flight VS4379 to LHR"].

The optional retainDataType parameter specifies whether the method should preserve the data type of all input values in the returned array. If retainDataType is set to false or omitted, in some situations, strings in the input array might be converted to numbers in the returned array. For example, if the selected values from the input array are "1", "2", and "3", the returned array might be [ 1, 2, 3 ]. To avoid unexpected type conversions, specify true for this parameter.

Complex templates

A more complex template might contain formatting that displays the information in a legible layout. For a complex template, you might want to store the template in a context variable, which you can then pass to the joinToArray method instead of a string.

For example, this complex template contains a subset of the array elements, adding labels and formatting:

<br/>Flight number: %e.flight% <br/> Airline: %e.carrier% <br/> Departure date: %e.departure_date% <br/> Departure time: %e.departure_time% <br/> Arrival time: %e.arrival_time% <br/>

Make sure any formatting that you use in your template is supported by the channel integration that displays the assistant output.

If you create a context variable that is called Template, and assign this template as its value, you can then use that variable in your expressions:

${Flight_data}.joinToArray(${Template})

At run time, the response would look like this:

Flight number: AZ1040
Airline: Alitalia
Departure date: 2019-02-02
Departure time: 16:45
Arrival time: 07:00

Flight number: DL1710
Airline: Delta
Departure date: 2019-02-02
Departure time: 07:00
Arrival time: 10:19

Flight number: VS4379
Airline: Virgin Atlantic
Departure date: 2019-02-02
Departure time: 21:40
Arrival time: 09:05

JSON Object templates

Instead of a string, you can define a template as a JSON object. This provides a way to standardize the formatting of information from different systems, or to transform data into the format required for an external service.

In this example, a template is defined as a JSON object that extracts flight details from the elements that are specified in the array that is stored in the Flight data context variable:

{
  "departure": "Flight %e.flight% departs on %e.departure_date% at %e.departure_time%.",
  "arrival": "Flight %e.flight% arrives on %e.arrival_date% at %e.arrival_time%."
}

Using this template, the joinToArray() method returns a new array of objects with the specified structure.

JSONArray.remove(Integer)

This method removes the element in the index position from the JSONArray and returns the updated JSONArray.

For this dialog runtime context:

{
  "context": {
    "toppings_array": ["onion", "olives"]
  }
}

Make this update:

{
  "context": {
    "toppings_array": "<? $toppings_array.remove(0) ?>"
  }
}

Result:

{
  "context": {
    "toppings_array": ["olives"]
  }
}

JSONArray.removeValue(object)

This method removes the first occurrence of the value from the JSONArray and returns the updated JSONArray.

For this dialog runtime context:

{
  "context": {
    "toppings_array": ["onion", "olives"]
  }
}

Make this update:

{
  "context": {
    "toppings_array": "<? $toppings_array.removeValue('onion') ?>"
  }
}

Result:

{
  "context": {
    "toppings_array": ["olives"]
  }
}

JSONArray.set(Integer index, Object value)

This method sets the input index of the JSONArray to the input value and returns the modified JSONArray.

For this dialog runtime context:

{
  "context": {
    "toppings_array": ["onion", "olives", "ham"]
  }
}

Dialog node output:

{
  "context": {
    "toppings_array": "<? $toppings_array.set(1,'ketchup')?>"
  }
}

Result:

{
  "context": {
    "toppings_array": ["onion", "ketchup", "ham"]
  }
}

JSONArray.size()

This method returns the size of the JSONArray as an integer.

For this dialog runtime context:

{
  "context": {
    "toppings_array": ["onion", "olives"]
  }
}

Make this update:

{
  "context": {
    "toppings_array_size": "<? $toppings_array.size() ?>"
  }
}

Result:

{
  "context": {
    "toppings_array_size": 2
  }
}

JSONArray split(String regexp)

This method splits the input string by using the input regular expression. The result is a JSONArray of strings.

For this input:

"bananas;apples;pears"

This syntax:

{
  "context": {
    "array": "<?input.text.split(";")?>
  }
}

Results in this output:

{
  "context": {
    "array": [ "bananas", "apples", "pears" ]
  }
}

com.google.gson.JsonArray support

In addition to the built-in methods, you can use standard methods of the com.google.gson.JsonArray class.

New array

new JsonArray().append('value')

To define a new array that is completed with values that are provided by users, you can instantiate an array. You must also add a placeholder value to the array when you instantiate it. You can use the following syntax to do so:

{
  "context":{
    "answer": "<? output.answer?:new JsonArray().append('temp_value') ?>"
  }

Date and Time

Several methods are available to work with date and time.

For information about how to recognize and extract date and time information from user input, see @sys-date and @sys-time entities.

The following string formats are supported for date-time literals on which the methods might be invoked.

  • For time only: HH:mm:ss or HH:mm
  • For date only: yyyy-MM-dd
  • For date and time: yyyy-MM-dd HH:mm:ss
  • For date and time with time zone: yyyy-MM-dd HH:mm:ss VV. The V symbol is from the DateTimeFormatter and represents a time zone in IANA Time Zone Database (TZDB) format, for example, Europe/London.

.after(String date or time)

Determines whether the date/time value is after the date/time argument.

.before(String date or time)

Determines whether the date/time value is before the date/time argument.

For example:

  • @sys-time.before('12:00:00')

  • @sys-date.before('2016-11-21')

  • If you compare different items, such as time vs. date, date vs. time, and time vs. date and time, the method returns false and an exception is printed in the response JSON log output.log_messages.

    For example, @sys-date.before(@sys-time).

  • If you compare date and time vs. time the method ignores the date and compares only times.

now(String time zone)

Returns a string with the current date and time in the format yyyy-MM-dd HH:mm:ss. Optionally specify a timezone value to get the current date and time for a specific time zone, with a returned string in the format yyyy-MM-dd HH:mm:ss 'GMT'XXX.

  • Static function.
  • The other date/time methods can be invoked on date-time values that are returned by this function and it can be passed in as their argument.
  • The user interface automatically creates a $timezone context variable for you so the correct time is returned when you test from the "Try it out" pane. If you don't pass a time zone, the time zone that is set automatically by the UI is used. Outside of the UI, GMT is used as the time zone. To learn about the syntax to use to specify the time zone, see Time zones that are supported by system entities.

Example of using now() to first check whether it's morning before the assistant responds with a morning-specific greeting.

{
  "conditions": "now().before('12:00:00')",
  "output": {
      "generic": [
        {
        "values": [
          {
          "text": "Good morning!"
          }
        ],
        "response_type": "text",
        "selection_policy": "sequential"
        }
      ]
  }
}

Example of using now() with a time zone to return the current time (in England):

{
  "output": {
      "generic": [
        {
        "values": [
          {
          "text": "The current date and time is: <? now('Europe/London') ?>"
          }
        ],
        "response_type": "text",
        "selection_policy": "sequential"
        }
      ]
  }
}

You can substitute the hardcoded time zone value with a context variable to dynamically change the time based on a time zone that is passed to the expression. For example: <? now('$myzone') ?>. The $myzone context variable might be set to 'Australia/Sydney' in one conversation and to 'Mexico/BajaNorte' in another.

.reformatDateTime(String format)

Formats date and time strings to the format you want for user output.

Returns a formatted string according to the specified format:

  • MM/dd/yyyy for 12/31/2016
  • h a for 10pm

To return the day of the week:

  • EEEE for Tuesday
  • E for Tue
  • u for day index (1 = Monday, ..., 7 = Sunday)

For example, this context variable definition creates a $time variable that saves the value 17:30:00 as 5:30 PM.

{
  "context": {
    "time": "<? @sys-time.reformatDateTime('h:mm a') ?>"
  }
}

The format follows the Java SimpleDateFormat rules.

Note: When you try to format time only, the date is treated as 1970-01-01.

.sameMoment(String date/time)

  • Determines whether the date/time value is the same as the date/time argument.

.sameOrAfter(String date/time)

  • Determines whether the date/time value is after or the same as the date/time argument.
  • Analogous to .after().

.sameOrBefore(String date/time)

  • Determines whether the date/time value is before or the same as the date/time argument.

today()

Returns a string with the current date in the format yyyy-MM-dd.

  • Static function.
  • The other date methods can be invoked on date values that are returned by this function and it can be passed in as their argument.
  • If the context variable $timezone is set, this function returns dates in the client's time zone. Otherwise, the GMT time zone is used.

Example of a dialog node with today() used in the output field:

{
  "conditions": "#what_day_is_it",
  "output": {
    "generic": [
      {
        "values": [
          {
          "text": "Today's date is <? today() ?>."
          }
        ],
        "response_type": "text",
        "selection_policy": "sequential"
      }
    ]
  }
}

Result: Today's date is 2018-03-09.

Date and time calculations

Use the following methods to calculate a date, where <date> is specified in the format yyyy-MM-dd or yyyy-MM-dd HH:mm:ss.

Method Description
<date>.minusDays(n) Returns the date of the day n number of days before the specified date.
<date>.minusMonths(n) Returns the date of the day n number of months before the specified date.
<date>.minusYears(n) Returns the date of the day n number of years before the specified date.
<date>.plusDays(n) Returns the date of the day n number of days after the specified date.
<date>.plusMonths(n) Returns the date of the day n number of months after the specified date.
<date>.plusYears(n) Returns the date of the day n number of years after the specified date.

To get tomorrow's date, specify the following expression:

{
  "output": {
    "generic": [
      {
        "values": [
          {
          "text": "Tomorrow's date is <? today().plusDays(1) ?>."
          }
        ],
        "response_type": "text",
        "selection_policy": "sequential"
      }
    ]
  }
}

Result if today is March 9, 2018: Tomorrow's date is 2018-03-10.

To get the date for the day a week from today, specify the following expression:

{
  "output": {
    "generic": [
      {
        "values": [
          {
          "text": "Next week's date is <? @sys-date.plusDays(7) ?>."
          }
        ],
        "response_type": "text",
        "selection_policy": "sequential"
      }
    ]
  }
}

Result if the date captured by the @sys-date entity is today's date, March 9, 2018: Next week's date is 2018-03-16.

To get last month's date, specify the following expression:

{
  "output": {
    "generic": [
      {
        "values": [
          {
          "text": "Last month the date was <? today().minusMonths(1) ?>."
          }
        ],
        "response_type": "text",
        "selection_policy": "sequential"
      }
    ]
  }
}

Result if today is March 9, 2018: Last month the date was 2018-02-9.

Use the following methods to calculate time, where <time> is specified in the format HH:mm:ss.

Method Description
<time>.minusHours(n) Returns the time n hours before the specified time.
<time>.minusMinutes(n) Returns the time n minutes before the specified time.
<time>.minusSeconds(n) Returns the time n seconds before the specified time.
<time>.plusHours(n) Returns the time n hours after the specified time.
<time>.plusMinutes(n) Returns the time n minutes after the specified time.
<time>.plusSeconds(n) Returns the time n seconds after the specified time.

To get the time an hour from now, specify the following expression:

{
  "output": {
    "generic": [
      {
        "values": [
          {
          "text": "One hour from now is <? now().plusHours(1) ?>."
          }
        ],
        "response_type": "text",
        "selection_policy": "sequential"
      }
    ]
  }
}

Result if it is 8 AM: One hour from now is 09:00:00.

To get the time 30 minutes ago, specify the following expression:

{
  "output": {
    "generic": [
      {
        "values": [
          {
          "text": "A half hour before @sys-time is <? @sys-time.minusMinutes(30) ?>."
          }
        ],
        "response_type": "text",
        "selection_policy": "sequential"
      }
    ]
  }
}

Result if the time captured by the @sys-time entity is 8 AM: A half hour before 08:00:00 is 07:30:00.

To reformat the time that is returned, you can use the following expression:

{
  "output": {
    "generic": [
      {
        "values": [
          {
          "text": "6 hours ago was <? now().minusHours(6).reformatDateTime('h:mm a') ?>."
          }
        ],
        "response_type": "text",
        "selection_policy": "sequential"
      }
    ]
  }
}

Result if it is 2:19 PM: 6 hours ago was 8:19 AM.

Working with time spans

To show a response based on whether today's date falls within a certain time frame, you can use a combination of time-related methods. For example, if you run a special offer during the holiday season every year, you can check whether today's date falls between November 25 and December 24 of this year. First, define the dates of interest as context variables.

In the following start and end date context variable expressions, the date is being constructed by concatenating the derived current year value with hardcoded month and day values.

"context": {
   "end_date": "<? now().reformatDateTime('Y') + '-12-24' ?>",
   "start_date": "<? now().reformatDateTime('Y') + '-11-25' ?>"
 }

In the response condition, you can indicate that you want to show the response only if the current date falls between the start and end dates that you defined as context variables.

now().after($start_date) && now().before($end_date)

java.util.Date support

In addition to the built-in methods, you can use standard methods of the java.util.Date class.

To get the date of the day that falls a week from today, you can use the following syntax.

{
  "context": {
    "week_from_today": "<? new Date(new Date().getTime() +
      (7 * (24*60*60*1000L))) ?>"
  }
}

This expression first gets the current date in milliseconds since January 1, 1970, 00:00:00 Coordinated Universal Time. It also calculates the number of milliseconds in 7 days. (The (24*60*60*1000L) represents one day in milliseconds.) It then adds 7 days to the current date. The result is the full date of the day that falls a week from today. For example, Fri Jan 26 16:30:37 UTC 2018. The time is in the Coordinated Universal Time time zone. You can always change the 7 to a variable ($number_of_days, for example) that you can pass in. Be sure that its value gets set before this expression is evaluated.

If you want to be able to compare the date with another date that is generated by the service, then you must reformat the date. System entities (@sys-date) and other built-in methods (now()) convert dates to the yyyy-MM-dd format.

{
  "context": {
    "week_from_today": "<? new Date(new Date().getTime() +
      (7 * (24*60*60*1000L))).format('yyyy-MM-dd') ?>"
  }
}

After you reformat the date, the result is 2018-01-26. Now, you can use an expression like @sys-date.after($week_from_today) in a response condition to compare a date that is specified in user input to the date saved in the context variable.

The following expression calculates the time 3 hours from now.

{
  "context": {
    "future_time": "<? new Date(new Date().getTime() + (3 * (60*60*1000L)) -
      (5 * (60*60*1000L))).format('h:mm a') ?>"
  }
}

The (60*60*1000L) value represents an hour in milliseconds. This expression adds 3 hours to the current time. It then recalculates the time from the Coordinated Universal Time zone to the EST time zone by subtracting 5 hours from it. It also reformats the date values to include hours and minutes AM or PM.

Numbers

These methods help you get and reformat number values.

For information about system entities that can recognize and extract numbers from user input, see @sys-number entity.

If you want the service to recognize specific number formats in user input, such as order number references, consider creating a pattern entity to capture it. See Creating entities for more details.

If you want to change the decimal placement for a number, to reformat a number as a currency value, for example, see the String format() method.

toDouble()

Converts the object or field to the Double number type. You can call this method on any object or field. If the conversion fails, null is returned.

toInt()

Converts the object or field to the Integer number type. You can call this method on any object or field. If the conversion fails, null is returned.

toLong()

Converts the object or field to the Long number type. You can call this method on any object or field. If the conversion fails, null is returned.

If you specify a Long number type in a SpEL expression, you must append an L to the number to identify it as such. For example, 5000000000L. This syntax is required for any numbers that do not fit into the 32-bit Integer type. For example, numbers that are greater than 2^31 (2,147,483,648) or less than -2^31 (-2,147,483,648) are considered Long number types. Long number types have a minimum value of -2^63 and a maximum value of 2^63-1 (or 9,223,372,036,854,775,807).

If you need to determine if a number is too long to be recognized, check if there are more than 18 integers in the number by using an expression like this:

<? @sys-number.toString().length() > 18 ?>

If you need to work with numbers that are longer than 18 integers, consider a pattern entity (with a regular expression such as \d{20}) to work with them instead of using @sys-number.

Standard math

Use SpEL expressions to define standard math equations, where the operators are represented by using these symbols:

Arithmetic operation Symbol
addition
division /
multiplication
subtraction

For example, in a dialog node response, you might add a context variable that captures a number that is specified in the user input (@sys-number), and saves it as $your_number. You can then add the following text as a text response:

I'm doing math. Given the value you specified ($your_number), when I add 5, I get: <? $your_number + 5 ?>. 
When I subtract 5, I get: <? $your_number - 5 ?>. 
When I multiply it by 5, I get: <? $your_number * 5 ?>. 
When I divide it by 5, I get: <? $your_number/5 ?>.

If the user specifies 10, then the resulting text response looks like this:

I'm doing math. Given the value you specified (10), when I add 5, I get: 15. 
When I subtract 5, I get: 5. 
When I multiply it by 5, I get: 50. 
When I divide it by 5, I get: 2.

Java number support

java.lang.Math()

Performs basic numeric operations.

You can use the Class methods:

max()

{
  "context": {
    "bigger_number": "<? T(Math).max($number1,$number2) ?>"
  },
  "output": {
    "generic": [
      {
        "values": [
          {
          "text": "The bigger number is $bigger_number."
          }
        ],
        "response_type": "text",
        "selection_policy": "sequential"
      }
    ]
  }
}

min()

{
  "context": {
    "smaller_number": "<? T(Math).min($number1,$number2) ?>"
  },
  "output": {
    "generic": [
      {
        "values": [
          {
          "text": "The smaller number is $smaller_number."
          }
        ],
        "response_type": "text",
        "selection_policy": "sequential"
      }
    ]
  }
}

pow()

{
  "context": {
    "power_of_two": "<? T(Math).pow($base.toDouble(),2.toDouble()) ?>"
  },
  "output": {
    "generic": [
      {
        "values": [
          {
          "text": "Your number $base to the second power is $power_of_two."
          }
        ],
        "response_type": "text",
        "selection_policy": "sequential"
      }
    ]
  }
}

For more information, see the java.lang.Math reference documentation.

java.util.Random()

Returns a random number. You can use one of the following syntax options:

  • To return a random Boolean value (true or false), use <?new Random().nextBoolean()?>.
  • To return a random double number between 0 (included) and 1 (excluded), use <?new Random().nextDouble()?>.
  • To return a random integer between 0 (included) and a number you specify, use <?new Random().nextInt(n)?> where n is the top of the number range you want + 1. For example, if you want to return a random number between 0 and 10, specify <?new Random().nextInt(11)?>.
  • To return a random integer from the full Integer value range (-2147483648 to 2147483648), use <?new Random().nextInt()?>.

For example, you might create a dialog node that is triggered by the #random_number intent. The first response condition might look like this:

Condition = @sys-number
{
  "context": {
    "answer": "<? new Random().nextInt(@sys-number.numeric_value + 1) ?>"
  },
  "output": {
    "generic": [
      {
        "values": [
          {
          "text": "Here's a random number between 0 and @sys-number.literal: $answer."
          }
        ],
        "response_type": "text",
        "selection_policy": "sequential"
      }
    ]
  }
}

See the java.util.Random reference documentation for information about other methods.

You can use standard methods of the following classes also:

  • java.lang.Byte
  • java.lang.Integer
  • java.lang.Long
  • java.lang.Double
  • java.lang.Short
  • java.lang.Float

Objects

JSONObject.clear()

This method clears all values from the JSON object and returns null.

For example, you want to clear the current values from the $user context variable.

{
  "context": {
    "user": {
      "first_name":"John",
      "last_name":"Snow"
    }
  }
}

Use the following expression in the output to define a field that clears the object of its values.

{
  "output": {
    "object_eraser": "<? $user.clear() ?>"
  }
}

If you reference the $user context variable, it returns {} only.

You can use the clear() method on the context or output JSON objects in the body of the API /message call.

Clearing context

When you use the clear() method to clear the context object, it clears all variables except these ones:

  • context.conversation_id
  • context.timezone
  • context.system

Warning: All context variable values means:

  • All default values that were set for variables in nodes that were triggered during the current session.
  • Any updates that are made to the default values with information that is provided by the user or external services during the current session.

To use the method, you can specify it in an expression in a variable that you define in the output object. For example:

{
  "output": {
    "generic": [
      {
        "values": [
          {
          "text": "Response for this node."
          }
        ],
        "response_type": "text",
        "selection_policy": "sequential"
      }
    ],
    "context_eraser": "<? context.clear() ?>"
  }
}

Clearing output

When you use the clear() method to clear the output object, it clears all variables except the one you use to clear the output object and any text responses that you define in the current node. It also does not clear these variables:

  • output.nodes_visited
  • output.nodes_visited_details

To use the method, you can specify it in an expression in a variable that you define in the output object. For example:

{
  "output": {
    "generic": [
      {
        "values": [
          {
          "text": "Have a great day!"
          }
        ],
        "response_type": "text",
        "selection_policy": "sequential"
      }
    ],
    "output_eraser": "<? output.clear() ?>"
  }
}

If a node earlier in the tree defines a text response of I'm happy to help. and then jumps to a node with the JSON output object defined earlier, then only Have a great day. is displayed as the response. The I'm happy to help. output is not displayed because it is cleared and replaced with the text response from the node that is calling the clear() method.

JSONObject.has(String)

This method returns true if the complex JSONObject has a property of the input name.

For this dialog runtime context:

{
  "context": {
    "user": {
      "first_name": "John",
      "last_name": "Snow"
    }
  }
}

Dialog node output:

{
  "conditions": "$user.has('first_name')"
}

Result: The condition is true because the user object contains the property first_name.

JSONObject.remove(String)

This method removes a property of the name from the input JSONObject. The JSONElement that is returned by this method is the JSONElement that is being removed.

For this dialog runtime context:

{
  "context": {
    "user": {
      "first_name": "John",
      "last_name": "Snow"
    }
  }
}

Dialog node output:

{
  "context": {
    "attribute_removed": "<? $user.remove('first_name') ?>"
  }
}

Result:

{
  "context": {
    "user": {
      "last_name": "Snow"
    },
    "attribute_removed": {
      "first_name": "John"
    }
  }
}

com.google.gson.JsonObject support

In addition to the built-in methods, some of the standard methods of the com.google.gson.JsonObject class are also supported.

Strings

These methods help you work with text.

For information about how to recognize and extract certain types of Strings, such as people names and locations, from user input, see System entities.

Note: For methods that involve regular expressions, see RE2 Syntax reference for details about the syntax to use when you specify the regular expression.

String.append(Object)

This method appends an input object to the string as a string and returns a modified string.

For this dialog runtime context:

{
  "context": {
    "my_text": "This is a text."
  }
}

This syntax:

{
  "context": {
    "my_text": "<? $my_text.append(' More text.') ?>"
  }
}

Results in this output:

{
  "context": {
    "my_text": "This is a text. More text."
  }
}

String.contains(String)

This method returns true if the string contains the input substring.

Input: "Yes, I'd like to go."

This syntax:

{
  "conditions": "input.text.contains('Yes')"
}

Results: The condition is true.

String.endsWith(String)

This method returns true if the string ends with the input substring.

For this input:

"What is your name?".

This syntax:

{
  "conditions": "input.text.endsWith('?')"
}

Results: The condition is true.

String.equals(String)

This method returns true if the specified string equals the input string exactly.

Input: "Yes"

This syntax:

{
  "conditions": "input.text.equals('Yes')"
}

Results: The condition is true.

If the input is Yes., then the result is false because the user included a period and the expression expects only the exact text, Yes without any punctuation.

String.equalsIgnoreCase(String)

This method returns true if the specified string equals the input string, regardless of whether the casing of the letters matches.

Input: "yes"

This syntax:

{
  "conditions": "input.text.equalsIgnoreCase('Yes')"
}

Results: The condition is true.

If the input is Yes., then the result is false because the user included a period and the expression expects only the text Yes, in uppercase or lowercase letters without any punctuation.

String.extract(String regexp, Integer groupIndex)

This method returns a string from the input that matches the regular expression group pattern that you specify. It returns an empty string if no match is found.

This method is designed to extract matches for different regex pattern groups, not different matches for a single regex pattern. To find different matches, see the getMatch method.

In this example, the context variable is saving a string that matches the regex pattern group that you specify. In the expression, two regex patterns groups are defined, each one enclosed in parentheses. An inherent third group is composed of the two groups. This is the first (groupIndex 0) regex group; it matches with a string that contains the full number group and text group. The second regex group (groupIndex 1) matches with the first occurrence of a number group. The third group (groupIndex 2) matches with the first occurrence of a text group after a number group.

{
  "context": {
    "number_extract": "<? input.text.extract('([\\d]+)(\\b [A-Za-z]+)',n) ?>"
  }
}

When you specify the regex in JSON, you must provide two backslashes (\). If you specify this expression in a node response, you need one backslash only. For example:

<? input.text.extract('([\d]+)(\b [A-Za-z]+)',n) ?>

Input:

"Hello 123 this is 456".

Result:

  • When n=0, the value is 123 this.
  • When n=1, the value is 123.
  • When n=2, the value is this.

String.find(String regexp)

This method returns true if any segment of the string matches the input regular expression. You can call this method against a JSONArray or JSONObject element, and it converts the array or object to a string before it makes the comparison.

For this input:

"Hello 123456".

This syntax:

{
  "conditions": "input.text.find('^[^\d]*[\d]{6}[^\d]*$')"
}

Result: The condition is true because the numeric portion of the input text matches the regular expression ^[^\d]*[\d]{6}[^\d]*$.

String.getMatch(String regexp, Integer matchIndex)

This method returns a string from the input that matches the occurrence of the regular expression pattern that you specify. This method returns an empty string if no match is found.

As matches are found, they are added to what you can think of as a matches array. If you want to return the third match, because the array element count starts at 0, specify 2 as the matchIndex value. For example, if you enter a text string with three words that match the specified pattern, you can return the first, second, or third match only by specifying its index value.

In the following expression, you are looking for a group of numbers in the input. This expression saves the second pattern-matching string into the $second_number context variable because the index value 1 is specified.

{
  "context": {
    "second_number": "<? input.text.getMatch('([\\d]+)',1) ?>"
  }
}

If you specify the expression in JSON syntax, you must provide two backslashes (\). If you specify the expression in a node response, you need one backslash only.

For example:

<? input.text.getMatch('([\d]+)',1) ?>

  • User input:

    "hello 123 i said 456 and 8910".
    
  • Result: 456

In this example, the expression looks for the third block of text in the input.

<? input.text.getMatch('(\b [A-Za-z]+)',2) ?>

For the same user input, this expression returns and.

String.isEmpty()

This method returns true if the string is an empty string, but not null.

For this dialog runtime context:

{
  "context": {
    "my_text_variable": ""
  }
}

This syntax:

{
  "conditions": "$my_text_variable.isEmpty()"
}

Results: The condition is true.

String.length()

This method returns the character length of the string.

For this input:

"Hello"

This syntax:

{
  "context": {
    "input_length": "<? input.text.length() ?>"
  }
}

Results in this output:

{
  "context": {
    "input_length": 5
  }
}

String.matches(String regexp)

This method returns true if the string matches the input regular expression.

For this input:

"Hello".

This syntax:

{
  "conditions": "input.text.matches('^Hello$')"
}

Result: The condition is true because the input text matches the regular expression \^Hello\$.

String.startsWith(String)

This method returns true if the string starts with the input substring.

For this input:

"What is your name?".

This syntax:

{
  "conditions": "input.text.startsWith('What')"
}

Results: The condition is true.

String.substring(Integer beginIndex, Integer endIndex)

This method gets a substring with the character at beginIndex and the last character set to be indexed before endIndex. The endIndex character is not included.

For this dialog runtime context:

{
  "context": {
    "my_text": "This is a text."
  }
}

This syntax:

{
  "context": {
    "my_text": "<? $my_text.substring(5, $my_text.length()) ?>"
  }
}

Results in this output:

{
  "context": {
    "my_text": "is a text."
  }
}

String.toJson()

This method parses a string that contains JSON data and returns a JSON object or array, as in this example:

${json_var}.toJson()

If the context variable ${json_var} contains the following string:

"{ \"firstname\": \"John\", \"lastname\": \"Doe\" }"

The toJson() method returns the following object:

{
  "firstname": "John",
  "lastname": "Doe"
}

String.toLowerCase()

This method returns the original string that is converted to lowercase letters.

For this input:

"This is A DOG!"

This syntax:

{
  "context": {
    "input_lower_case": "<? input.text.toLowerCase() ?>"
  }
}

Results in this output:

{
  "context": {
    "input_lower_case": "this is a dog!"
  }
}

String.toUpperCase()

This method returns the original string that is converted to uppercase letters.

For this input:

"hi there".

This syntax:

{
  "context": {
    "input_upper_case": "<? input.text.toUpperCase() ?>"
  }
}

Results in this output:

{
  "context": {
    "input_upper_case": "HI THERE"
  }
}

String.trim()

This method trims any spaces at the beginning and the end of the string and returns the modified string.

For this dialog runtime context:

{
  "context": {
    "my_text": "   something is here    "
  }
}

This syntax:

{
  "context": {
    "my_text": "<? $my_text.trim() ?>"
  }
}

Results in this output:

{
  "context": {
    "my_text": "something is here"
  }
}

java.lang.String support

In addition to the built-in methods, you can use standard methods of the java.lang.String class.

java.lang.String.format()

You can apply the standard Java String format() method to text. See java.util.formatter reference for information about the syntax to use to specify the format details.

For example, the following expression takes three decimal integers (1, 1, and 2) and adds them to a sentence.

{
  "formatted String": "<? T(java.lang.String).format('%d + %d equals %d', 1, 1, 2) ?>"
}

Result: 1 + 1 equals 2.

To change the decimal placement for a number, use the following syntax:

{
  <? T(String).format('%.2f',<number to format>) ?>
}

For example, if the $number variable that needs to be formatted in US dollars is 4.5, then a response such as Your total is $<? T(String).format('%.2f',$number) ?> returns Your total is $4.50..

Indirect data type conversion

When you include an expression within text, as part of a node response, for example, the value is rendered as a String. If you want the expression to be rendered in its original data type, then do not surround it with text.

For example, you can add this expression to a dialog node response to return the entities that are recognized in the user input in String format:

  The entities are <? entities ?>.

If the user specifies Hello now as the input, then the @sys-date and @sys-time entities are triggered by the now reference. The entities object is an array, but because the expression is included in text, the entities are returned in String format, like this:

  The entities are 2018-02-02, 14:34:56.

If you do not include text in the response, then an array is returned instead. For example, if the response is specified as an expression only, not surrounded by text.

  <? entities ?>

The entity information is returned in its original data type, as an array.

[
  {
    "entity":"sys-date","location":[6,9],"value":"2018-02-02","confidence":1,"metadata":{"calendar_type":"GREGORIAN","timezone":"America/New_York"}
  },
  {
    "entity":"sys-time","location":[6,9],"value":"14:33:22","confidence":1,"metadata":{"calendar_type":"GREGORIAN","timezone":"America/New_York"}
  }
  ]

As another example, the following $array context variable is an array, but the $string_array context variable is a string.

{
  "context": {
    "array": [
      "one",
      "two"
    ],
    "array_in_string": "this is my array: $array"
  }
}

If you check the values of these context variables in the Try it out pane, you see their values that are specified as follows:

$array : ["one","two"]

$array_in_string : "this is my array: [\"one\",\"two\"]"

You can perform array methods on the $array variable, such as <? $array.removeValue('two') ?>, but not the $array_in_string variable.