%dw 2.0 import * from dw::core::Strings output application/json var userName: String = "John"
Type System
DataWeave version 2 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:
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.
%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
.
%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
:
%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"
).
%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:
%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:
%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:
%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:
%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.
%dw 2.0 output application/json var user: {|firstName: String, lastName: String|} = {firstName: "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:
%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:
%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:
%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"})
}