Reading Time: 12 minutes

Logic handling using DataWeave is essential for simple mediums and highly complex transformations, in which the mapping requirements necessitate generating outputs based on values provided in the input payload.  

This could be as simple as checking whether certain field(s)/attribute(s) in the input payload are empty or null, then setting a corresponding field(s)/attribute(s) to a certain value. It could also involve the ability to set the structure/values of the output payload based on whether a collection/array in the input payload is empty or not. In addition, it could be as complex as having different set of mappings, conditionally based on the value of certain fields/attributes.

latest report
Learn why we are the Leaders in API management and iPaaS

This HowTo guide will provide examples of how to do the following:

  1. Build if/else logic based on logical conditions on input payload fields
  2. Check fields for nulls
  3. Handle empty XML nodes

Step 1: Build If/Else Logic

1. In this step, you will build if/else logic based on logical conditions on input payload fields. In order to implement conditional mappings in DataWeave with if/else logic, you will provide the when/or/otherwise syntax. To illustrate this, we will look at two DataWeave snippets for an airline flight scheduling scenario.

The following is a simple if/else scenario, where we check the input for a specific value and set the output accordingly. Otherwise, we default the value to something else when the input doesn’t match the condition:

(FlightType: "DIVERT" when payload.flightDetail.state == "DIV"
otherwise
FlightType: "SCHEDULED"),

2. Below is a more complex case in which there are many mappings between input and output values. Here, we are setting the value of a field called “FlightState” to 6 possible values, which are based on 11 possible values that are sent in a field called “payload.flightDetail.state”:

(FlightStatus: "SCHEDULED") when payload.flightDetail.state == "SKD"
or payload.flightDetail.state == "ETD"
or payload.flightDetail.state == "NXI"
or payload.flightDetail.state == "CNL"
or payload.flightDetail.state == "RSK",
(FlightStatus: "OUTGATE") when payload.flightDetail.state == "OUT",
(FlightStatus: "OFFGROUND") when payload.flightDetail.state == "DEP"
or payload.flightDetail.state == "DIV",
(FlightStatus: "ONGROUND") when payload.flightDetail.state == "ON",
(FlightStatus: "INGATE") when payload.flightDetail.state == "ARR",
(FlightStatus: "RETURN_TO_GATE") when payload.flightDetail.state == "RTR",

Step 2: Checking fields for nulls

1. In DataWeave, when a field in the input payload has a value and you append a question mark (“?”) at the end of the field, it will return true.  On the other hand, if the field is null, then it would return false.  Going back to our airline flight scheduling scenario, below is an example in which the airline needs to generate an output field called “OperEstDepTime,” but only when a particular input field is not null.  

Flight Details:

{
FlightNumber: payload.flightDetail.flightnum,
(OperEstDepTime: payload.flightDetail.schedule.estimatedTimeDeparture)
                               when payload.flightDetail.schedule.estimatedTimeDeparture?,
FlightState: payload.flightDetail.state
}

Step 3: Handling empty XML nodes

1. Transforming XML payloads can be tricky if you need to handle scenarios where you are mapping an XML collection, which is potentially empty. To illustrate this, consider a scenario where you have an XML payload that represents an invoice.  This object has a set of header levels at the root node, referred to as “Invoice-header.” It also has a collection of “Invoice-lines,” which represent individual line items on the invoice.  Within each “Invoice-line,” there could be either no Account allocation fields or many of them.

Input:

<?xml version="1.0"?>
<invoice-header>
<id type="integer">61</id>
<created-at type="datetime">2015-04-07T15:59:48-04:00</created-at>
<updated-at type="datetime">2015-04-07T16:03:45-04:00</updated-at>
<comments nil="true"/>
<compliant type="boolean">false</compliant>
<handling-amount type="decimal">0.0</handling-amount>
<internal-note nil="true"/>
<invoice-date type="datetime">2015-04-07T00:00:00-04:00</invoice-date>
<invoice-number>test1</invoice-number>
<line-level-taxation type="boolean">false</line-level-taxation>
<misc-amount type="decimal">0.0</misc-amount>
<shipping-amount type="decimal">0.0</shipping-amount>
<status>approved</status>
<supplier-total type="decimal">0.00</supplier-total>
<supplier-note/>
<discount-due-date nil="true"/>
<net-due-date type="datetime">2015-05-07T00:00:00-04:00</net-due-date>
<discount-amount nil="true"/>
<tax-code nil="true"/>
<tax-rate nil="true"/>
<tax-amount type="decimal">3.00</tax-amount>
<tolerance-failures/>
<paid type="boolean">false</paid>
<payment-date nil="true"/>
<payment-notes/>
<exported type="boolean">true</exported>
<tax-amount-engine nil="true"/>
<last-exported-at type="datetime">2015-04-07T16:16:59-04:00</last-exported-at>
<image-scan>ABC-test.coupahost.com/invoice_headers/image_scans/61/original/BABC_-_Smart_Phone_Authorization_Request_form-signed.pdf</image-scan>
<image-scan-url/>
<supplier-created type="boolean">false</supplier-created>
<invoice-lines type="array">
<invoice-line>
<id type="integer">55</id>
<created-at type="datetime">2015-04-07T15:59:48-04:00</created-at>
<updated-at type="datetime">2015-04-07T16:03:45-04:00</updated-at>
<accounting-total type="decimal">1901.19</accounting-total>
<description>LASERJET ENTERPRISE 500 MFP MFP M525F</description>
<line-num type="integer">1</line-num>
<order-header-num type="integer">40</order-header-num>
<po-number>40</po-number>
<order-line-id type="integer">79</order-line-id>
<billing-note nil="true"/>
<order-line-num type="integer">1</order-line-num>
<price type="decimal">1901.19</price>
<net-weight nil="true"/>
<price-per-uom type="decimal">0.00</price-per-uom>
<quantity type="decimal">1.0</quantity>
<status>matched</status>
<tax-rate nil="true"/>
<tax-location nil="true"/>
<tax-amount type="decimal">0.00</tax-amount>
<tax-description nil="true"/>
<tax-supply-date nil="true"/>
<total type="decimal">1901.19</total>
<type>InvoiceQuantityLine</type>
<tax-amount-engine nil="true"/>
<use-tax-code/>
<approval-flag/>
<account>
<id type="integer">44</id>
<created-at type="datetime">2015-01-04T21:36:57-05:00</created-at>
<updated-at type="datetime">2015-05-05T12:56:42-04:00</updated-at>
<name>ABC-Accts Payable: Am Clearing-Product Design</name>
<code>ABC00-10-211017-10-03500</code>
<active type="boolean">false</active>
<segment-1>ABC00</segment-1>
<segment-2>10</segment-2>
<segment-3>211017</segment-3>
<segment-4>10</segment-4>
<segment-5>03500</segment-5>
<segment-6 nil="true"/>
<segment-7 nil="true"/>
<segment-8 nil="true"/>
<segment-9 nil="true"/>
<segment-10 nil="true"/>
<segment-11 nil="true"/>
<segment-12 nil="true"/>
<segment-13 nil="true"/>
<segment-14 nil="true"/>
<segment-15 nil="true"/>
<segment-16 nil="true"/>
<segment-17 nil="true"/>
<segment-18 nil="true"/>
<segment-19 nil="true"/>
<segment-20 nil="true"/>
</account>
<account-allocations type="array"/>
</invoice-line>
</invoice-lines>
</invoice-header>

2. If we were to simply map the fields in DataWeave as follows – and we do not handle the possibility that <account-allocations> is in fact null – then the code would involve the following:

%dw 1.0
%output application/xml
---
{

Invoice: {
Invoice-header: {
// Iterates through the collection for all the invoice-lines with the invoice-header 
Invoice-Lines: payload.invoice-header.invoice-lines mapObject 
{
Invoice-Line: {
Account: {
code: $.invoice-line.account.code,
segment1: $.account.segment-1,
segment2: $.account.segment-2,
segment3: $.account.segment-3,
segment4: $.account.segment-4,
segment5: $.account.segment-5,
segment6: $.account.segment-6,
segment7: $.account.segment-7,
segment8: $.account.segment-8,
segment9: $.account.segment-9,
segment10: $.account.segment-10
},
description: $.description,
line-num: $.line-num,
po-number: $.po-number,
// Iterates through the collection for all the Account-Allocations                 
Account-Allocations: payload.invoice-header.invoice-lines.account-allocations map
                 {
Account-Allocation: {
Account: {
code: $.account.code,
segment1: $.account.segment-1,
segment2: $.account.segment-2,
segment3: $.account.segment-3,
segment4: $.account.segment-4,
segment5: $.account.segment-5,
segment6: $.account.segment-6,
segment7: $.account.segment-7,
segment8: $.account.segment-8,
segment9: $.account.segment-9,
segment10: $.account.segment-10
},
line-num: $.line-num
}
}
} 
},
InvoiceNumber: payload.invoice-header.invoice-number,
Invoice-date: payload.invoice-header.invoice-date,
last-exported-at: payload.invoice-header.last-exported-at,
Invoice-Total: payload.invoice-header.tax-amount + payload.invoice-header.shipping-amount + payload.invoice-header.handling-amount + payload.invoice-header.misc-amount,
Supplier: {
name: payload.invoice-header.supplier.name,
number: payload.invoice-header.supplier.number
}
}
 }
}

3. You will then see the following exception on the map operator:

ERROR 2017-07-01 16:03:39,059 [[responsetimetracker].HTTP_Listener_Configuration.worker.01] org.mule.exception.DefaultMessagingExceptionStrategy:

***************************************************************

Message               : Exception while executing:

Account-Allocations: payload.invoice-header.invoice-lines.account-allocations map

                    ^

Type mismatch for ‘map’ operator

    found :null, :function

 required :array, :function.

***************************************************************

To handle the fact that <account-allocations> is empty in the sample input above, before we map the Account related fields that can potentially be sent in the input payload we need to use the when/otherwise structure to check if <account-allocations> is not null, blank, or contains a blank collection represented by “[]”:

Account-Allocations: payload.invoice-header.invoice-lines.account-allocations map
               {
Account-Allocation: {
Account: {
code: $.account.code,
segment1: $.account.segment-1,
segment2: $.account.segment-2,
segment3: $.account.segment-3,
segment4: $.account.segment-4,
segment5: $.account.segment-5,
segment6: $.account.segment-6,
segment7: $.account.segment-7,
segment8: $.account.segment-8,
segment9: $.account.segment-9,
segment10: $.account.segment-10
},
line-num: $.line-num
}
}
} when (payload.invoice-header.invoice-lines.account-allocations != null) and (payload.invoice-header.invoice-lines.account-allocations != "") and (payload.invoice-header.invoice-lines.account-allocations != []) 
                otherwise {
Account-Allocations: {
// There are no account-allocations
}
}
}

4. After these steps, we can correctly generate the output payload, even when the <account-allocations> collection in the input payload is blank as shown above.  DataWeave will then generate the following output successfully:

Output:

<?xml version='1.0' encoding='UTF-8'?>
<Invoice>
    <Invoice-header>
        <Invoice-Lines>
            <Invoice-Line>
                <Account-Allocations/>
            </Invoice-Line>
        </Invoice-Lines>
        <InvoiceNumber>test1</InvoiceNumber>
        <Invoice-date>2015-04-07T00:00:00-04:00</Invoice-date>
        <last-exported-at>2015-04-07T16:16:59-04:00</last-exported-at>
        <Invoice-Total>3.00</Invoice-Total>
        <Supplier>
            <name xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
            <number xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
        </Supplier>
    </Invoice-header>
</Invoice>

In providing logic handling features, DataWeave enables Mule developers to easily build complex mappings in order to implement a myriad of use cases in a quick and intuitive fashion.  With logic handling, teams can effectively shorten development time and, in turn, achieve faster time to market.

For more information, check our Mule User Guide for DataWeave and discover the role of DataWeave in Mule 4.