MuleSoft

Transformari MuleSoft DataWeave: ghid complet

Petru Constantin
--10 min lectura
#mulesoft#dataweave#data-transformation#api-integration#anypoint-platform

DataWeave este limbajul de transformare al MuleSoft. Acest ghid acopera totul, de la transformari de baza pana la tehnici avansate care rezolva provocari reale de integrare.

Concepte fundamentale DataWeave

Sa intelegem conceptele de baza ale DataWeave:

%dw 2.0
output application/json
 
// Basic transformation structure
{
    // Header section defines output format
    // Body section contains transformation logic
}

Tipuri de date si conversii

%dw 2.0
output application/json
 
var inputData = {
    "stringNum": "123",
    "dateString": "2025-01-15",
    "boolString": "true",
    "nullValue": null
}
 
---
{
    // Type coercion examples
    asNumber: inputData.stringNum as Number,
    asDate: inputData.dateString as Date {format: "yyyy-MM-dd"},
    asBoolean: inputData.boolString as Boolean,
 
    // Default values for null handling
    withDefault: inputData.nullValue default "N/A",
 
    // Conditional coercion
    safeNumber: if (inputData.stringNum is String)
                    inputData.stringNum as Number
                else
                    0
}

Transformari JSON catre XML

Converteste payload-uri JSON in XML cu namespace-uri:

%dw 2.0
output application/xml
ns soap http://schemas.xmlsoap.org/soap/envelope/
ns ord http://example.com/orders
 
var orderJson = {
    "orderId": "ORD-12345",
    "customer": {
        "id": "CUST-001",
        "name": "Acme Corp",
        "email": "orders@acme.com"
    },
    "items": [
        {"sku": "PROD-A", "quantity": 10, "price": 25.99},
        {"sku": "PROD-B", "quantity": 5, "price": 49.99}
    ],
    "total": 509.85
}
---
{
    soap#Envelope: {
        soap#Header: {},
        soap#Body: {
            ord#CreateOrder: {
                ord#OrderHeader: {
                    ord#OrderId: orderJson.orderId,
                    ord#OrderDate: now() as String {format: "yyyy-MM-dd'T'HH:mm:ss"},
                    ord#Customer: {
                        ord#CustomerId: orderJson.customer.id,
                        ord#CustomerName: orderJson.customer.name,
                        ord#Email: orderJson.customer.email
                    }
                },
                ord#OrderLines: {
                    (orderJson.items map (item, index) -> {
                        ord#OrderLine: {
                            ord#LineNumber: index + 1,
                            ord#SKU: item.sku,
                            ord#Quantity: item.quantity,
                            ord#UnitPrice: item.price,
                            ord#LineTotal: item.quantity * item.price
                        }
                    })
                },
                ord#OrderTotal: orderJson.total
            }
        }
    }
}

Transformari XML catre JSON

Gestioneaza XML complex cu atribute si namespace-uri:

%dw 2.0
output application/json
 
var xmlInput = read('<?xml version="1.0"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    <soap:Body>
        <GetCustomerResponse xmlns="http://example.com/customer">
            <Customer id="CUST-001" status="active">
                <Name>Acme Corporation</Name>
                <Addresses>
                    <Address type="billing" primary="true">
                        <Street>123 Main St</Street>
                        <City>New York</City>
                        <Country>USA</Country>
                    </Address>
                    <Address type="shipping">
                        <Street>456 Warehouse Blvd</Street>
                        <City>Newark</City>
                        <Country>USA</Country>
                    </Address>
                </Addresses>
                <Contacts>
                    <Contact role="primary">John Doe</Contact>
                    <Contact role="billing">Jane Smith</Contact>
                </Contacts>
            </Customer>
        </GetCustomerResponse>
    </soap:Body>
</soap:Envelope>', "application/xml")
 
---
{
    customer: {
        // Access attributes with @
        id: xmlInput.Envelope.Body.GetCustomerResponse.Customer.@id,
        status: xmlInput.Envelope.Body.GetCustomerResponse.Customer.@status,
        name: xmlInput.Envelope.Body.GetCustomerResponse.Customer.Name,
 
        // Transform addresses array
        addresses: xmlInput.Envelope.Body.GetCustomerResponse.Customer.Addresses.*Address map (addr) -> {
            addressType: addr.@type,
            isPrimary: addr.@primary default "false" == "true",
            street: addr.Street,
            city: addr.City,
            country: addr.Country
        },
 
        // Transform contacts with role mapping
        contacts: xmlInput.Envelope.Body.GetCustomerResponse.Customer.Contacts.*Contact map (contact) -> {
            role: contact.@role,
            name: contact
        }
    }
}

Procesarea fisierelor CSV si flat file

Gestioneaza CSV cu delimitatori si headere personalizate:

%dw 2.0
output application/json
 
var csvData = "employee_id|first_name|last_name|department|salary|hire_date
E001|John|Doe|Engineering|85000|2020-03-15
E002|Jane|Smith|Marketing|72000|2019-08-22
E003|Bob|Johnson|Engineering|92000|2018-01-10
E004|Alice|Williams|Sales|78000|2021-05-01"
 
var parsedCsv = read(csvData, "application/csv", {
    separator: "|",
    header: true
})
---
{
    totalEmployees: sizeOf(parsedCsv),
 
    // Group by department
    byDepartment: parsedCsv groupBy $.department mapObject ((employees, dept) -> {
        (dept): {
            count: sizeOf(employees),
            totalSalary: sum(employees.salary map ($ as Number)),
            avgSalary: avg(employees.salary map ($ as Number)),
            employees: employees map {
                id: $.employee_id,
                name: $.first_name ++ " " ++ $.last_name,
                salary: $.salary as Number
            }
        }
    }),
 
    // Find highest paid
    highestPaid: (parsedCsv orderBy -(($.salary) as Number))[0] then {
        name: $.first_name ++ " " ++ $.last_name,
        salary: $.salary as Number,
        department: $.department
    }
}

Generare output CSV

%dw 2.0
output application/csv separator=",", header=true, quoteValues=true
 
var jsonOrders = [
    {orderId: "O001", customer: "Acme", product: "Widget A", qty: 10, price: 25.99},
    {orderId: "O002", customer: "Beta Inc", product: "Widget B", qty: 5, price: 49.99},
    {orderId: "O003", customer: "Acme", product: "Widget C", qty: 20, price: 15.50}
]
---
jsonOrders map {
    "Order ID": $.orderId,
    "Customer Name": $.customer,
    "Product": $.product,
    "Quantity": $.qty,
    "Unit Price": $.price,
    "Line Total": $.qty * $.price,
    "Order Date": now() as String {format: "yyyy-MM-dd"}
}

Operatii avansate pe array-uri

Stapaneste manipularile complexe de array-uri:

%dw 2.0
output application/json
 
var salesData = [
    {region: "North", product: "A", q1: 100, q2: 150, q3: 120, q4: 180},
    {region: "North", product: "B", q1: 80, q2: 90, q3: 110, q4: 95},
    {region: "South", product: "A", q1: 200, q2: 180, q3: 220, q4: 250},
    {region: "South", product: "B", q1: 150, q2: 160, q3: 140, q4: 170},
    {region: "East", product: "A", q1: 90, q2: 100, q3: 95, q4: 110},
    {region: "West", product: "A", q1: 120, q2: 130, q3: 125, q4: 140}
]
 
// Helper function for annual total
fun annualTotal(record) = record.q1 + record.q2 + record.q3 + record.q4
---
{
    // Flatten quarterly data to rows
    flattenedData: salesData flatMap (record) -> [
        {region: record.region, product: record.product, quarter: "Q1", sales: record.q1},
        {region: record.region, product: record.product, quarter: "Q2", sales: record.q2},
        {region: record.region, product: record.product, quarter: "Q3", sales: record.q3},
        {region: record.region, product: record.product, quarter: "Q4", sales: record.q4}
    ],
 
    // Pivot: Region totals by quarter
    regionQuarterPivot: salesData groupBy $.region mapObject ((records, region) -> {
        (region): {
            Q1: sum(records.q1),
            Q2: sum(records.q2),
            Q3: sum(records.q3),
            Q4: sum(records.q4),
            Annual: sum(records map annualTotal($))
        }
    }),
 
    // Product performance ranking
    productRanking: (salesData groupBy $.product mapObject ((records, product) -> {
        (product): sum(records map annualTotal($))
    }) pluck ((value, key) -> {product: key, totalSales: value}))
        orderBy -$.totalSales,
 
    // Find best quarter per region
    bestQuarterByRegion: salesData groupBy $.region mapObject ((records, region) -> {
        (region): do {
            var totals = {
                Q1: sum(records.q1),
                Q2: sum(records.q2),
                Q3: sum(records.q3),
                Q4: sum(records.q4)
            }
            var maxVal = max(totals pluck $)
            ---
            (totals filterObject ((v, k) -> v == maxVal) pluck $$)[0]
        }
    }),
 
    // Distinct regions and products
    distinctRegions: salesData.region distinctBy $,
    distinctProducts: salesData.product distinctBy $,
 
    // Cross join for all combinations
    allCombinations: (salesData.region distinctBy $) flatMap ((region) ->
        (salesData.product distinctBy $) map ((product) -> {
            region: region,
            product: product
        })
    )
}

Gestionarea cheilor dinamice

Lucreaza cu chei dinamice si variabile:

%dw 2.0
output application/json
 
var dynamicData = {
    "field_001": "value1",
    "field_002": "value2",
    "custom_abc": "valueA",
    "custom_xyz": "valueB",
    "metadata_created": "2025-01-15",
    "metadata_updated": "2025-01-20"
}
 
var keyMapping = {
    "field_001": "primaryField",
    "field_002": "secondaryField"
}
---
{
    // Rename keys dynamically
    renamedFields: dynamicData mapObject ((value, key) -> {
        ((keyMapping[key as String] default key)): value
    }),
 
    // Group by key prefix
    groupedByPrefix: dynamicData
        pluck ((value, key) -> {key: key, value: value})
        groupBy ((item) -> (item.key as String) splitBy "_")[0]
        mapObject ((items, prefix) -> {
            (prefix): items reduce ((item, acc = {}) -> acc ++ {
                (((item.key as String) splitBy "_")[1 to -1] joinBy "_"): item.value
            })
        }),
 
    // Filter keys by pattern
    customFieldsOnly: dynamicData filterObject ((v, k) ->
        (k as String) startsWith "custom_"
    ),
 
    // Dynamic key construction
    constructedObject: ["alpha", "beta", "gamma"] reduce ((item, acc = {}) ->
        acc ++ {("dynamic_" ++ item): upper(item)}
    )
}

Gestionarea erorilor si validarea datelor

Pattern-uri robuste de gestionare a erorilor:

%dw 2.0
output application/json
 
var inputPayload = {
    "orders": [
        {"id": "O1", "amount": "100.50", "currency": "USD", "date": "2025-01-15"},
        {"id": "O2", "amount": "invalid", "currency": "EUR", "date": "2025-01-16"},
        {"id": "O3", "amount": "200.00", "currency": null, "date": "bad-date"},
        {"id": null, "amount": "150.00", "currency": "GBP", "date": "2025-01-17"}
    ]
}
 
// Validation function
fun validateOrder(order) = {
    isValid: (order.id != null) and
             (order.amount is String and (order.amount matches /^\d+(\.\d{2})?$/)) and
             (order.currency != null) and
             (order.date is String and (order.date matches /^\d{4}-\d{2}-\d{2}$/)),
    errors: (
        (if (order.id == null) ["Missing order ID"] else []) ++
        (if (!(order.amount is String and (order.amount matches /^\d+(\.\d{2})?$/))) ["Invalid amount format"] else []) ++
        (if (order.currency == null) ["Missing currency"] else []) ++
        (if (!(order.date is String and (order.date matches /^\d{4}-\d{2}-\d{2}$/))) ["Invalid date format"] else [])
    )
}
 
// Safe type conversion
fun safeToNumber(val) =
    if (val is String and (val matches /^-?\d+(\.\d+)?$/))
        val as Number
    else
        null
 
fun safeToDate(val, format) =
    try (() -> val as Date {format: format})
    orElse null
---
{
    // Validate all orders
    validationResults: inputPayload.orders map ((order, index) -> {
        orderIndex: index,
        order: order,
        validation: validateOrder(order)
    }),
 
    // Separate valid and invalid
    validOrders: inputPayload.orders filter ((order) -> validateOrder(order).isValid),
 
    invalidOrders: inputPayload.orders
        map ((order, index) -> {order: order, index: index, validation: validateOrder(order)})
        filter ((item) -> !item.validation.isValid)
        map {
            orderIndex: $.index,
            originalData: $.order,
            errors: $.validation.errors
        },
 
    // Transform with safe conversions
    processedOrders: inputPayload.orders map ((order) -> {
        id: order.id default "UNKNOWN",
        amount: safeToNumber(order.amount) default 0,
        currency: order.currency default "USD",
        date: safeToDate(order.date, "yyyy-MM-dd"),
        processed: true
    }),
 
    // Summary
    summary: {
        total: sizeOf(inputPayload.orders),
        valid: sizeOf(inputPayload.orders filter ((o) -> validateOrder(o).isValid)),
        invalid: sizeOf(inputPayload.orders filter ((o) -> !validateOrder(o).isValid))
    }
}

Functii personalizate si module

Creeaza module DataWeave reutilizabile:

// File: src/main/resources/dw/CommonFunctions.dwl
%dw 2.0
 
// String utilities
fun capitalize(str: String): String =
    upper(str[0]) ++ lower(str[1 to -1])
 
fun toTitleCase(str: String): String =
    str splitBy " " map capitalize($) joinBy " "
 
fun truncate(str: String, maxLen: Number): String =
    if (sizeOf(str) > maxLen)
        str[0 to maxLen - 4] ++ "..."
    else
        str
 
// Date utilities
fun formatDateISO(date: Date): String =
    date as String {format: "yyyy-MM-dd'T'HH:mm:ss'Z'"}
 
fun daysBetween(date1: Date, date2: Date): Number =
    (date2 - date1) as Number {unit: "days"}
 
fun isBusinessDay(date: Date): Boolean = do {
    var dayOfWeek = date as String {format: "E"}
    ---
    !(dayOfWeek == "Sat" or dayOfWeek == "Sun")
}
 
// Currency formatting
fun formatCurrency(amount: Number, currency: String): String = do {
    var symbols = {
        "USD": "\$",
        "EUR": "€",
        "GBP": "£"
    }
    ---
    (symbols[currency] default currency ++ " ") ++
    (amount as String {format: "#,##0.00"})
}
 
// UUID generation (simplified)
fun generateId(prefix: String): String =
    prefix ++ "-" ++ uuid() splitBy "-" joinBy ""
 
// Null-safe navigation
fun safeGet(obj: Object, path: String): Any =
    path splitBy "." reduce ((key, acc = obj) ->
        if (acc is Object) acc[key] else null
    )

Utilizarea modulelor personalizate

%dw 2.0
output application/json
 
// Import custom module
import * from dw::CommonFunctions
 
var customerData = {
    name: "john doe",
    email: "JOHN.DOE@EXAMPLE.COM",
    description: "This is a very long description that should be truncated for display purposes in the UI",
    orderDate: "2025-01-15" as Date,
    orderTotal: 1234.56
}
---
{
    displayName: toTitleCase(customerData.name),
    email: lower(customerData.email),
    shortDescription: truncate(customerData.description, 50),
    orderDateFormatted: formatDateISO(customerData.orderDate),
    daysSinceOrder: daysBetween(customerData.orderDate, now()),
    totalFormatted: formatCurrency(customerData.orderTotal, "USD"),
    transactionId: generateId("TXN")
}

Optimizarea performantei

Optimizeaza DataWeave pentru payload-uri mari:

%dw 2.0
output application/json
 
// Use streaming for large files
// output application/json streaming=true
 
var largeDataset = (1 to 10000) as Array map {
    id: $,
    value: random() * 1000,
    category: ["A", "B", "C", "D"][mod($, 4)]
}
 
---
{
    // GOOD: Use reduce instead of multiple iterations
    optimizedAggregation: largeDataset reduce ((item, acc = {
        count: 0,
        sum: 0,
        categories: {}
    }) -> {
        count: acc.count + 1,
        sum: acc.sum + item.value,
        categories: acc.categories ++ {
            (item.category): (acc.categories[item.category] default 0) + 1
        }
    }),
 
    // GOOD: Filter early, transform later
    filteredFirst: largeDataset
        filter ($.value > 500)  // Reduce dataset size first
        map {                    // Then transform
            id: $.id,
            roundedValue: round($.value)
        },
 
    // GOOD: Use distinctBy with specific field
    uniqueCategories: largeDataset.category distinctBy $,
 
    // Limit output for previews
    preview: largeDataset[0 to 9]
}
 
/*
Sfaturi de performanta:
1. Foloseste streaming pentru fisiere > 10MB
2. Filtreaza inainte de a transforma
3. Evita loop-urile imbricate - foloseste groupBy si reduce
4. Foloseste evaluarea lazy cu defer
5. Minimizeaza conversiile de tip in interiorul loop-urilor
*/

Concluzie

Abordarea functionala a DataWeave face transformarile complexe elegante si usor de intretinut. Stapaneste conversia tipurilor, operatiile pe array-uri si functiile personalizate pentru a gestiona orice scenariu de integrare. Foloseste module pentru reutilizare si respecta bunele practici de performanta pentru workload-uri de productie. Cu aceste tehnici, poti aborda orice provocare de transformare in MuleSoft.


Sistemul tau AI e conform cu EU AI Act? Evaluare gratuita de risc - afla in 2 minute →

Ai nevoie de ajutor cu conformitatea EU AI Act sau securitatea AI?

Programeaza o consultatie gratuita de 30 de minute. Fara obligatii.

Programeaza un Apel

Weekly AI Security & Automation Digest

Get the latest on AI Security, workflow automation, secure integrations, and custom platform development delivered weekly.

No spam. Unsubscribe anytime.