Contact Free trial Login

Type System

DataWeave 2.0 supports a type system. To take advantage of the type-checking that the type system executes, you need to provide constraint expressions for variables and functions described in this section. For information about how values for these types are defined, refer to Value Constructs for Types.

A type system defines a set of constraints to a set of constructs, such as:

  • Variables

  • Function parameters

These constraints are used in the type-checking phase, when DataWeave ensures that the values assigned to variables or the arguments for a function call respect its constraints, for example:

DataWeave Script
%dw 2.0
import * from dw::core::Strings
output application/json

var userName: String = "John"

The variable definition for userName has the constraint of assigning a value of type String. If some other value type is assigned, a type-checking error is raised.

DataWeave Script
%dw 2.0
import * from dw::core::Strings
output application/json

fun toUser(id: Number, userName: String): String = "you called the function toUser!"

The constraints to call the function toUser are that the first argument has to be of type Number, the second one of type String (to be coercible to those types). Another constraint is that the result of the function has to be of type String. This constraint isn’t used in the call for toUser; it’s applied to its definition to validate that the function body generates the proper type.

toUser(123, "John")
toUser("123", true)

Although you might expect the second call to fail, autocoercion enables the call to be successful, and both calls work. In the case of the second function call, there is autocoercion of the String value "123" to the Number value 123, and the Boolean value true to the String value "true", which makes it possible to call the function.
An example of a call that throws a type-checking error is toUser("a 12", "John"), because the String value "a 12" isn’t coercible to a Number type.

DataWeave also uses a global type inference algorithm to validate your code even if no types are specified as constraints.

Although the use of constraints is optional, they can be useful in big scripts or multiple scripts. The type system helps you to avoid bugs in your DataWeave logic because it causes the type-checking algorithm to run. Additionally, when you write your script in an IDE and define these constraints, the tooling helps you to find type checking errors, instead of when the script is running and fails.

The types supported by the DataWeave type system are divided into three categories:

  • Simple Types

  • Composite Types

  • Complex Types

Simple Types

Simple types represent values such as strings, Booleans, and so on. These values are atomic; they are not composed of other values. These are the simple types:

  • String

  • Boolean

  • Number

  • Regex

  • Null

  • Temporal: Date, DateTime, LocalDateTime, LocalTime, Time, Period

Null

Null is a type of only one value, the value null, which means that null cannot be assigned to the type String or any other type except for the type Null.

For example, the repeat function in the String module has the signature repeat(String, Number): String, which means that the function accepts as a first parameter only a value of type String, and as a second parameter only a value of type Number. The function returns a value of type String.

DataWeave Script
%dw 2.0
import * from dw::core::Strings
output application/json
---
repeat("a", 3)

This returns "aaa", but the following example throws an error because the value null cannot be assigned to the parameter that expects a value of type String:

DataWeave Script
%dw 2.0
import * from dw::core::Strings
output application/json
---
repeat(null, 3)

In the following example, the script assigns to the first parameter a value that is the type Null (the type of this value is Type<Null>). It returns "NullNullNull" because autocoercion coerces the value to the type String ("Null").

DataWeave Script
%dw 2.0
import * from dw::core::Strings
output application/json
---
repeat(Null, 3)

Some functions have an overloaded signature (the function has multiple definitions for different parameter types) that let you call it with null.

For example, the isNumeric function from dw::core::Strings has the signature isNumeric(String): Boolean, which receives a string and successfully returns a result if that string is numeric, but the function is also defined as isNumeric(Null): Boolean.

So, if you call the function isNumeric with null (isNumeric(null)), the function does not throw an exception. At runtime, the function that is dispatching algorithm selects the correct function based on the types of the values you use. The function returns false because of the definition of isNumeric for null as the argument.

Composite Types

A composite type contains other values. The composite types are Array, Object and Function.

Array Type

An array is of the type Array<T> in which T is a type parameter that defines the type of the elements inside the array. For example, the syntax to define a variable with the type Array of Number is:

var idsList: Array<Number> = [1, 22, 333, 4444]

The variable idsList passes the type-checking phase only when the array that is assigned to it only contains values of the Number type.

Object Type

You can define an Object type in two ways: Object or {}. Both define an open object and don’t specify any constraint to their key-value pairs. See also Close and Open Objects.

The syntax for defining an Object type with a set of constraints for its key-value pairs is very similar to the syntax for defining the value of an Object type:

{
    keyName @(attrName: AttrType): valueType,
    ...
}

For example, the syntax to define a User type with firstName String, lastName String, and age Number is:

DataWeave Script
%dw 2.0
output application/json

type User = {
    firstName: String,
    lastName: String,
    age: Number
}

You can apply modifiers to each key-value pair to specify when a field is always present or when a field can be repeated.

For repeated fields use *. For example, the following type allows you to have multiple lastName fields:

DataWeave Script
%dw 2.0
output application/json

{
    firstName: String,
    lastName*: String,
    age: Number
}

For conditional fields, use ?. For example, the following type allows the age field to be present or not:

DataWeave Script
%dw 2.0
output application/json

{
    firstName: String,
    lastName*: String,
    age?: Number
}

Close and Open Objects

Object type can be either closed or open. A closed object accepts an object value only if there are no fields except the key-value pairs specified by the type.

An open object only put constraints on the fields that are declared in the type, and if they are all verified, then the type accepts the value. The type accepts objects that have other fields apart of the ones mentioned in the declaration. All object types are open unless specified.

The following example succeds even if age is not in the type declaration because the type of the variable user is an open object:

DataWeave Script
%dw 2.0
output application/json

var user: {firstName: String, lastName: String} = {firstName: "John", lastName: "Smith", age: 34}

The syntax to specify a closed object is {| |}. The following example makes the User type support only the keys firstName and lastName. The script throws an exception because the age field is not accepted.

DataWeave Script
%dw 2.0
output application/json

var user: {|firstName: String, lastName: String|} = {name: "John", lastName: "Smith", age: 34}

Function Type

DataWeave is a functional language, and functions are considered first class citizens, which means that functions are values with associated types.

The syntax to define a function type is:

(paramType: ParamType,...) -> ReturnType

For example, if you want to define in a constraint a Function type that has a parameter of type String, a second parameter of type Number, and a Boolean return value, the correct syntax is:

(paramA: String, paramB: Number) -> Boolean

In the following example, you define a function that receives another function as an argument:

DataWeave Script
%dw 2.0
output application/json

fun applyIDsChange(ids: Array<Number>, changeTo: (Number) -> Number): Array<Number> = ???

If you call the function applyIDsChange with a function that does not match the changeTo constraint (Number) → Number, DataWeave throws a type-checking error.

For example, the following call works because abs is a function that takes a value of the Number type and returns a Number, which matches the changeTo parameter:

applyIDsChange([1,-6, 3, -8], abs)

But the following call fails because sum is a function that takes an Array and returns a Number, so it does not match the parameter constraint:

applyIDsChange([1,-6, 3, -8], sum)

Complex Types

Complex types include the Any and Nothing types, the Union type, the Intersection type, and Literal types, each of which is named intuitively.

Any and Nothing

In some cases, you cannot enforce a restriction because the type accepts all the value:

  • Any type accepts all possible values.

  • Nothing type accepts no value, but it can be assigned to all the types. This type is not frequently used explicitly, but it is used by the type inference algorithm.

Union Type

The Union type is used to compose types. The syntax to define a Union type is:

TypeA | TypeB | ...

The following example defines a variable with a constraint that accepts a value of type String or Number:

var age: String | Number = if (payload.allStrings) "32" else 32

A common pattern is to use Null type with a Union type to specify that a type accepts null as a value. In the following example, the function parseEmail allows inputs of type String or Null. In this case, you can have a object with a payload optional field email and you call the function with payload.email.

fun parseEmail(email: String | Null) = "Code that handles email being of type String or Null"

Intersection Type

Introduced in DataWeave 2.3.0. Supported by Mule 4.3 and later.

The Intersection type intersects Object types. In this case, the intersection works as the concatenation (++) of object types.

The syntax for the Intersection type is:

TypeA & TypeB & ...

In the following example, the Intersection concatenates the two Object types, resulting in the type {name: String, lastName: String}. The type is an open object that can accept additional key-value pairs. The variable accepts the value assigned to it:

var a: {name: String} & {lastName: String} = {name: "John", lastName: "Smith", age: 34}

In the case of closed objects, it returns the concatenation of the object types but results in a closed object.
In the following example, the intersection results in the type {|name: String, lastName: String|}, which throws an exception because a closed object does not accept an Object value if there are additional fields in the object (field age):

var a: {|name: String|} & {|lastName: String|} = {name: "John", lastName: "Smith", age: 34}

Literal Types

Introduced in DataWeave 2.3.0. Supported by Mule 4.3 and later.

A literal type represents exactly one value. For example, the String value "foo" can be represented with the type "foo".

The following literal types are included to the type system:

  • String literal types

  • Number literal types

  • Boolean literal types

You can use literal types with Union types to declare a type as a finite set of allowed values. For example, the following type declarations are aliases of Union and literal types:

type Weekdays = "Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday"
type Days = Weekdays | "Saturday" | "Sunday"

In the following example, the variable accepts only a value of the literal types 404 and 500. The type system ensures before runtime that the variable can be only one of those literal values:

DataWeave Script
%dw 2.0
output application/json

var errorStatusCode: 404 | 500 = payload.statusCode match {
    case "error 404" -> 404
    case "error 500" -> 500
}

Function overloading enables you to define different behaviors based on the input argument’s value:

DataWeave Script
%dw 2.0
import * from dw::core::Strings
output application/json

fun errorHandling(errorCode: 404 | 405, response): String = "Code for error 4XX handling here!"
fun errorHandling(errorCode: 500 | 501, response): String = "Code for error 5XX handling here!"
---
errorHandling(payload.statusCode, payload)

At runtime, the function-dispatching algorithm selects the correct function based on the type of the value of payload.statusCode. It is not necessary to check the value of an argument with an if statement to change the behavior of the function.

Type Parameters

A type parameter enables you to write a function or data type generically so that you can handle values identically, without depending on the type of the values.

For example, you can define a function that returns the first element of an array without losing its type information. In the example, the variable definitions firstString:String and firstNumber:Number specify the type of the item in the array that the head function takes as input.

%dw 2.0
fun head<ItemType>(elements: Array<ItemType>): ItemType = elements[0]!

var firstString:String = head(["DataWeave", "Java", "Scala", "Haskell"]) ++ " Rules!!!"
var firstNumber:Number = head([1,2,3])
output application/json
---
firstNumber

In the body of the script, firstNumber returns the number 1. If you replace firstNumber with firstString, the script returns the string "DataWeave Rules!!!".

Binding Type Parameters

You can bind type parameters to require a value of a specific type. Appending {name: "DataWeave"} in the following example is valid because {name: String} in the function definition indicates that the value of the name key must be a string.

%dw 2.0

fun addName<User <: {} >(user: User): User & {name: String} = user ++ {name: "DataWeave"}

var myUser: {name: String, developers: Number} = addName({developers: 3})

Parameterizing Type Definitions

You can use type parameters to paramaterize type definitions. For example, assume that you define a data structure that models file content, one for text files and the other for binary files. You can use type parameters to construct new types that dispatch the correct implementation.

%dw 2.0

type FileData<Content> = {
    data: Content,
    name: String
}

fun read(file: FileData<String>) =  "This is a text file with data " ++ file.data
fun read(file: FileData<Binary>) =  "This is a binary file with data " ++ (file.data as String {base: "64"})

---
{
  binary: read({data: "Hello World" as Binary, name: "myFile.bin"}),
  text: read({data: "Hello World", name: "myFile.txt"})
}

Was this article helpful?

💙 Thanks for your feedback!

Edit on GitHub