Migrate SpEL Expressions to JSONiq

This guide shows how to rewrite SpEL expressions in JSONiq.

Identify Affected Integration Flows

JSONiq is the preferred language for new expressions. SpEL remains the default and is supported.

Safe SpEL evaluation rejects some SpEL expressions, such as those that call a method or refer to a type with T(...). The Expressions Guide lists what it rejects.

An integration flow with a rejected expression fails to deploy. Rewrite these expressions first.

Allow Unsafe SpEL Evaluation

An operator can set the flow-server property mc.flow-server.featureToggles.allowUnsafeSpelEvaluation to true. This turns on unsafe SpEL evaluation, which runs rejected expressions too. The property applies to every integration flow on the flow-server. It does not affect JSONiq expressions.

Set the property as the Flow Server Configuration Reference describes. Remove it once all rejected expressions are rewritten.

Rewrite a SpEL Expression

To rewrite a SpEL expression:

  1. Add the language prefix jsoniq:.

  2. Start each path at #input. For example, envelope.payload becomes #input.envelope.payload.

  3. Return the type the property expects. JSONiq does not convert results.

  4. Run the integration flow’s functional tests.

For example, this target filter of the distribute processor is rejected, because it calls contains:

targetFilterExpression("exchangePropertiesMap['documentType'] == 'invoice' and targetFlowId.contains('-invoices-')")

The JSONiq version:

targetFilterExpression("""jsoniq:#input.exchangePropertiesMap.documentType = "invoice" and contains(#input.targetFlowId, "-invoices-")""")

The tables below show SpEL expressions and their JSONiq versions. Each row covers one shape, such as a map key or a method call on a string. Rejected expressions are marked Rejected. The expressions use the standard expression context. Target filters also use targetFlowId.

The expressions are shown without the surrounding Kotlin string. In Kotlin, write a JSONiq expression containing double quotes as a raw string, between """ and """, so the quotes need no escaping as \". Where the expression ends in a double quote, add a space before the closing """:

filter {
    id = "filter-ok-responses"
    expression = """jsoniq:#input.exchangePropertiesMap.statusCode = "200" """
}

Reading Data

All SpEL expressions in this section are accepted.

Shape SpEL, then JSONiq Notes

A payload field

envelope.payload.header.noun

jsoniq:#input.envelope.payload.header.noun

A map key

envelope.payload['orderId']

jsoniq:#input.envelope.payload.orderId

SpEL’s dotted form, envelope.payload.orderId, fails on a missing key. JSONiq gives an empty result either way.

A stash entry

envelope.stash['savedOrder']

jsoniq:#input.envelope.stash.savedOrder

An exchange property

exchangePropertiesMap['orderStatus']

jsoniq:#input.exchangePropertiesMap.orderStatus

A name that holds a dot

exchangePropertiesMap['http.header.x-correlation-id']

jsoniq:#input.exchangePropertiesMap."http.header.x-correlation-id"

Quote names that hold dots. Without quotes, the result is empty.

A list element

envelope.payload.meterIds[0]

jsoniq:#input.envelope.payload.meterIds[[1]]

JSONiq arrays start at 1.

The size of a list

envelope.payload.files.size

jsoniq:size(#input.envelope.payload.files)

Whether a list is empty

envelope.payload.files.empty

jsoniq:empty(#input.envelope.payload.files[])

[] turns the array into a sequence for empty().

Operators

All SpEL expressions in this section are accepted.

Shape SpEL, then JSONiq Notes

Equality

exchangePropertiesMap['statusCode'] == '200'

jsoniq:#input.exchangePropertiesMap.statusCode = "200"

= returns false for a missing field. eq returns an empty result.

Not null

envelope.payload['nextPage'] != null

jsoniq:#input.envelope.payload.nextPage != null

Null

envelope.payload['nextPage'] == null

jsoniq:not(#input.envelope.payload.nextPage != null)

True for a missing field and for null. Exchange properties are never null, so empty(#input.exchangePropertiesMap.orderStatus) is enough for them.

and

envelope.payload['result'] == 'OK' and targetFlowId == 'orders-accepted'

jsoniq:#input.envelope.payload.result = "OK" and #input.targetFlowId = "orders-accepted"

or and !

!(exchangePropertiesMap['orderStatus'] == 'cancelled' or exchangePropertiesMap['orderStatus'] == 'rejected')

jsoniq:not(#input.exchangePropertiesMap.orderStatus = "cancelled" or #input.exchangePropertiesMap.orderStatus = "rejected")

JSONiq uses not() for both ! and not.

Ternary operator

envelope.payload.priority == 'high' ? 'express' : 'standard'

jsoniq:if (#input.envelope.payload.priority = "high") then "express" else "standard"

Elvis operator

exchangePropertiesMap['contentType'] ?: 'text/plain'

jsoniq:(#input.exchangePropertiesMap.contentType, "text/plain")[1]

[1] picks the first item: the property if it exists, otherwise the default. SpEL also replaces an empty string. JSONiq keeps it.

A chain of Elvis operators

exchangePropertiesMap['http.header.x-request-id'] ?: (exchangePropertiesMap['http.header.x-correlation-id'] ?: envelope.messageId)

jsoniq:(#input.exchangePropertiesMap."http.header.x-request-id", #input.exchangePropertiesMap."http.header.x-correlation-id", #input.envelope.messageId)[1]

The first value that exists wins. Names that hold dots are quoted.

Safe navigation

envelope.payload['contact']?.email

jsoniq:#input.envelope.payload.contact.email

A missing field gives an empty result, so JSONiq needs no ?..

String concatenation

exchangePropertiesMap['documentName'] + '.json'

jsoniq:concat(#input.exchangePropertiesMap.documentName, ".json")

For a missing property, SpEL gives null.json and JSONiq gives .json.

matches

envelope.payload.meterIds[0] matches '73[0-9]+'

jsoniq:matches(#input.envelope.payload.meterIds[[1]], "^73[0-9]+$")

SpEL matches the whole string. JSONiq matches any part, so anchor the pattern with ^ and $. Arrays start at 1.

Method Calls on Strings

All SpEL expressions in this section are rejected, because they call methods.

Shape SpEL, then JSONiq Notes

toString

envelope.payload.responseCode.toString()

jsoniq:string(#input.envelope.payload.responseCode)

Rejected.

indexOf

envelope.payload.indexOf('ORDER_ID') < 0

jsoniq:not(contains(#input.envelope.payload, "ORDER_ID"))

Rejected. Here the payload is a string.

substring before the last dot

exchangePropertiesMap['fileName'].substring(0, exchangePropertiesMap['fileName'].lastIndexOf('.'))

jsoniq:replace(#input.exchangePropertiesMap.fileName, "[.][^.]*$", "")

Rejected. For a name without a dot, SpEL fails and JSONiq returns the whole name.

substring after the last dot

exchangePropertiesMap['fileName'].substring(exchangePropertiesMap['fileName'].lastIndexOf('.') + 1)

jsoniq:tokenize(#input.exchangePropertiesMap.fileName, "[.]")[last()]

Rejected.

startsWith

envelope.payload.meterIds[0].startsWith('73') and targetFlowId == 'readings-north'

jsoniq:starts-with(#input.envelope.payload.meterIds[[1]], "73") and #input.targetFlowId = "readings-north"

Rejected. Arrays start at 1.

contains

exchangePropertiesMap['http.path.info'].contains('/attachments') and !exchangePropertiesMap['http.path.info'].contains('/binary')

jsoniq:contains(#input.exchangePropertiesMap."http.path.info", "/attachments") and not(contains(#input.exchangePropertiesMap."http.path.info", "/binary"))

Rejected. Names that hold dots are quoted.

toUpperCase

envelope.payload.country.toUpperCase()

jsoniq:upper-case(#input.envelope.payload.country)

Rejected.

equals

envelope.payload.status.equals('OK')

jsoniq:#input.envelope.payload.status eq "OK"

Rejected. For a missing field, SpEL fails and eq returns an empty result. Use = to get false instead.

Method Calls on Collections

All SpEL expressions in this section are rejected, because they call methods. The .size and .empty properties are accepted and give the same results.

Shape SpEL, then JSONiq Notes

size()

envelope.payload.files.size() > 0

jsoniq:size(#input.envelope.payload.files) > 0

Rejected. size() counts array members. count() counts sequence items.

isEmpty()

not envelope.payload['timeSeries']['items'].isEmpty()

jsoniq:exists(#input.envelope.payload.timeSeries.items[])

Rejected.

Type References

All SpEL expressions in this section are rejected, because they use T(...). The Utilihive functions replace them.

Shape SpEL, then JSONiq Notes

A random UUID

T(java.util.UUID).randomUUID().toString()

jsoniq:uh:uuid4()

Rejected.

The current date and time

T(java.time.LocalDateTime).now()

jsoniq:uh:formattedCurrentDateTime("yyyy-MM-dd'T'HH:mm:ss", "Europe/Oslo")

Rejected. Returns a string. Pass the flow-server’s time zone to match SpEL. Without it, the time is UTC.

The current date in a path

'orders/' + T(java.time.LocalDateTime).now().format(T(java.time.format.DateTimeFormatter).ISO_LOCAL_DATE) + '/' + envelope.messageId

jsoniq:concat("orders/", uh:formattedCurrentDateTime("yyyy-MM-dd", "Europe/Oslo"), "/", #input.envelope.messageId)

Rejected. Pass the flow-server’s time zone to match SpEL. Without it, the date is UTC.

No JSONiq Equivalent

All SpEL expressions in this section are rejected. JSONiq has no equivalent, because a JSONiq expression only returns data. Use a processor instead.

Shape SpEL Instead

A constructor

new java.util.HashMap(envelope.payload)

Rejected. JSONiq returns JSON data, not Java objects. Build objects with the map processor. A small object can be returned directly, as in jsoniq:{ "orderId": #input.envelope.payload.orderId, "status": "sent" }.

An assignment to a payload field

envelope.payload['status'] = 'sent'

Rejected. Change the payload with the map processor.

An assignment to an exchange property

exchangePropertiesMap['orderStatus'] = 'shipped'

Rejected. Use the setExchangeProperty processor, as shown below.

put on the exchange properties map

exchangePropertiesMap.put('orderStatus', 'shipped')

Rejected. Use the setExchangeProperty processor, as shown below.

This setExchangeProperty processor sets orderStatus to shipped:

setExchangeProperty {
    id = "set-order-status"
    propertyName = "orderStatus"
    source = """jsoniq:"shipped" """
}

Check for Behavior Changes

A valid JSONiq rewrite can still behave differently from the SpEL expression: