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 to true 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

application/vnd.solace.​micro-integration.unspecified

Unspecified

The content type is unspecified and cannot be read nor modified.

application/json

Structured

The content type is interpreted/serialized as JSON.

['<key>']

To access a JSON object property.

[<index>]

To access a JSON array element.

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 to application/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 to application/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 to application/json, you can write expressions to access elements within the JSON source payload such as source['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

source['headers']

Read-Only

A map of key-value pairs representing the headers of the source message.

source['payload']

Read-Only

The payload of the source message as interpreted by source-payload.content-type.

target['headers']

Write-Only

A map of key-value pairs representing the headers of the target message.

target['payload']

Write-Only

The payload of the target message as interpreted by target-payload.content-type.

var

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:

  1. Builds a dynamic routing header using the flight details

  2. 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"
}