Mapping Message Headers and Payloads
You can map header and payload values between your source and target systems. Mapping allows you to define the relationships between the data fields in your source system and the data fields in your target system. If you don't explicitly map any source headers, their values are not propagated to the target. If you don't map any payload fields, the payload is passed through to the target system unchanged.
You map header and payload fields between your source and target systems using an expression language based on Spring Expression Language (SpEL). In addition, you can use transformation functions to modify the source header or payload data so that it conforms to your target format.
The mappings are specified in the transform
configuration section of the application.yml
file, as shown below, where:
-
<workflow-id>
is a value between 0 and 19 -
<expression>
is an expression that defines a transformation between the source and target message
solace:
connector:
workflows:
<workflow-id>:
enabled: true
transform:
enabled: true
source-payload:
content-type: "application/json"
target-payload:
content-type: "application/json"
expressions:
- transform: <expression>
- transform: <expression>
The properties under the solace.connector.workflows.<workflow-id>.transform
prefix are as follows:
-
enabled
—Must be set totrue
to enable the message transformation capabilities. -
source-payload.content-type
,target-payload.content-type
—The content type to use to interpret the source payload or target payload, respectively. For more information, see Content Type Interpretation. -
expressions
—An ordered list of transformation expressions to apply to the message. To learn more about expressions, see Writing Transformation Expressions.
For a detailed example that shows how to map and transform the headers and payload a specific message, see Mapping and Transformation Example.
Content Type Interpretation
The source-payload.content-type
and target-payload.content-type
properties define how the source and target payloads are interpreted and serialized.
Content types are not data types. Content types determine how the physical data is logically interpreted by the connector. For example, a JSON payload can be interpreted from any payload whose data type is a string, a byte array, or a map.
The following content types are supported:
Content Type | Content Type Category | Description |
---|---|---|
|
Unspecified |
The content type is unspecified and cannot be read nor modified. |
|
Structured |
The content type is interpreted/serialized as JSON.
|
The source-payload.content-type
and target-payload.content-type
are handled as separate and independent operations:
-
The content type interpretation of the source payload determines how the source payload is interpreted for reading within transformations.
For example, when the
source-payload.content-type
is set toapplication/json
, the source payload is interpreted as JSON. -
The content type interpretation of the target payload determines how the target payload is interpreted for writing within transformations, and how it is later serialized for the output.
For example, when the
target-payload.content-type
is set toapplication/json
, the target payload is interpreted as JSON and will be later serialized as JSON.
Depending on the content type’s category, expressions can be written with varying levels of refinement:
-
Structured content types—When the content type is structured, you can write expressions that allow you to drill down into the structure of the source and target payloads.
For example, when the
source-payload.content-type
is set toapplication/json
, you can write expressions to access elements within the JSON source payload such assource['payload']['element'][0]['other']
, which corresponds to reading a JSON element using the JSON path$.element[0].other
. -
Unspecified content types—When the content type is unspecified, the source and target payloads are treated as opaque blobs of data, and can neither be written nor read.
Writing Transformation Expressions
To map the data coming from your source system to your target system, you supply a set of expressions in the configuration for each workflow. Specifically, under the solace.connector.workflows.<workflow-id>.transform
prefix, you set the following properties:
-
expressions
—An ordered list of transformation expressions to apply to the message. -
expressions[<index>].transform
—A single expression to transform the message.
A transformation expression can reference:
-
['payload']
to access the message payload. -
['headers']['<header_name>']
to access a message header value. -
transformation functions, which are built in and can be called directly from transformation expressions.
To call a function, use the
#
character followed by the function name. For a detailed list of functions, see the Transformation Function Reference.
We support and highly recommend using index notation (a['b']
) over dot notation (a.b
) for accessing nested properties within objects.
Dot notation has some limitations that can result in unexpected behavior. Notably, there is a restricted set of characters that can be used in property names. For example: a.b-c
is interpreted as "Get the value of b
from object a
, then subtract c
" instead of "Get the value of the property b-c
in object a
". Instead, use a['b-c']
, which works as expected.
By consistently using index notation, aside from escaping single and double quotes, you never need to worry about limitations like these.
The following context variables are available for you to use in your message transformation expressions:
Variable | Access | Description |
---|---|---|
|
Read-Only |
A map of key-value pairs representing the headers of the source message. |
|
Read-Only |
The payload of the source message as interpreted by |
|
Write-Only |
A map of key-value pairs representing the headers of the target message. |
|
Write-Only |
The payload of the target message as interpreted by |
|
Read/Write |
A map of key-value pairs that can be used to store intermediate values during the transformation. |
Header Propagation
Incoming message headers are not propagated to the target message by default. To propagate a header, you must define an expression that sets the header you want on the target message.
Example: Propagating a Source Header to the Target
This example propagates the my-header
header from the source message to the target message:
solace: connector: workflows: 0: transform: enabled: true expressions: - transform: "target['headers']['my-header'] = source['headers']['my-header']"
Payload Propagation
When no expressions reference target['payload']
, the payload is copied from the source message to the target message.
Payload propagation always adheres to the source-payload.content-type
and target-payload.content-type
settings. If the source-payload.content-type
and target-payload.content-type
are both set to application/json
, the payload is interpreted and serialized as JSON, even though no transformation expressions are defined. See Content Type Interpretation for details.
If you define at least one expression to set the target['payload']
or any element within the target payload, the payload is not copied from the source message to the target message, so you must build the target payload from scratch.
Example: Setting a Static Value
This example sets the city
element in the JSON target payload to Toronto
:
solace: connector: workflows: 0: transform: enabled: true source-payload: content-type: application/json target-payload: content-type: application/json expressions: - transform: "target['payload']['city'] = 'Toronto'"
The resultant target payload will be exactly {"city":"Toronto"}
regardless of what was in the source payload.
Example: Copying the Payload and Changing a Value
This example copies the JSON source payload to the target payload, then sets the office
element in the target payload to HQ
:
solace: connector: workflows: 0: transform: enabled: true source-payload: content-type: application/json target-payload: content-type: application/json expressions: # Copy the payload from the source to the target - transform: "target['payload'] = source['payload']" # Set the `office` element in the target payload to `HQ` - transform: "target['payload']['office'] = 'HQ'"
If the target field doesn't exist, the transformation adds it. For example, if the the source payload is {"a": "b"}
, then the target payload resulting from the two transforms above is {"a": "b", "office": "HQ"}
. As this example implies, you can also use this strategy to copy the source payload to the target payload and then modify parts of the target payload as needed.
Saving Intermediate Values
You can save intermediate values during the transformation process by using the var
variable.
By saving values in the var
variable, you avoid the need to recompute the same value or write complex expressions multiple times.
Example: Saving a Function Result
This example saves the return value from splitting the my-header
source header to the split-result
variable:
solace: connector: workflows: 0: transform: enabled: true expressions: # Split the `my-header` source header by `,` and save the result to the `split-result` variable - transform: "var['split-result'] = #splitString(source['headers']['my-header'], ',', 3)" # Set the `result-0` header to the first element of the split result - transform: "target['headers']['result-0'] = var['split-result'][0]" # Set the `result-1` header to the second element of the split result - transform: "target['headers']['result-1'] = var['split-result'][1]" # Set the `result-2` header to the third element of the split result - transform: "target['headers']['result-2'] = var['split-result'][2]"
Mapping and Transformation Example
Let’s look at a transformation example. Consider a JSON message payload with the following structure:
{ "airline": "ExampleAirline", "region": "Ontario", "requestId": 44334, "flight": { "flightModel": "boeing737", "flightRoute": "international" }, "origin": "yow", "destination": "ewr", "status": "boarding", "passengers": "300,250,10", "lastUpdated": "2024-01-05T14:30:00" }
To process this message, we’ll create a transformation that:
-
Builds a dynamic routing header using the flight details
-
Converts the comma-separated passenger field into a structured format
Here’s the transformation configuration:
solace: connector: workflows: 0: transform: enabled: true source-payload: content-type: application/json target-payload: content-type: application/json expressions: # Set the dynamic destination header to the value of `{airline}/{destination}/{origin}` from the source payload - transform: "target['headers']['scst_targetDestination'] = #joinString('/', source['payload']['airline'], source['payload']['destination'], source['payload']['origin'])" # Restructure the 'passengers' field from a comma-separated string into an object with three distinct fields - transform: "target['payload'] = source['payload']" - transform: "var['passengerDistribution'] = #splitString(source['payload']['passengers'], ',', 3)" - transform: "target['payload']['passengers'] = {:}" # Overwrite the 'passengers' field with an empty object - transform: "target['payload']['passengers']['capacity'] = #convertStringToNumber(var['passengerDistribution'][0])" - transform: "target['payload']['passengers']['occupied'] = #convertStringToNumber(var['passengerDistribution'][1])" - transform: "target['payload']['passengers']['personnel'] = #convertStringToNumber(var['passengerDistribution'][2])"
After applying this transformation, the output message header would be:
scst_targetDestination: ExampleAirline/ewr/yow
The output message payload would be:
{ "airline": "ExampleAirline", "region": "Ontario", "requestId": 44334, "flight": { "flightModel": "boeing737", "flightRoute": "international" }, "origin": "yow", "destination": "ewr", "status": "boarding", "passengers": { "capacity": 300, "occupied": 250, "personnel": 10 }, "lastUpdated": "2024-01-05T14:30:00" }