PowerConnect for SAP Solutions
Auto Light Dark
Auto Light Dark

Field mapping

Every input ships with a field mapping: the rules that turn a raw SAP API response into the flat event that is sent to Splunk or Dynatrace. The mapping says where each value is read from, what type it is, and what the field is called in the output.

The built-in mappings cover the standard fields of each API. You only need to edit a mapping when you want something the default does not give you - typically a custom header, a custom field added to an SAP object, or different field names to match an existing index.

Edit it in the UI under Inputs → your input → Mapping

How a mapping is applied

Three things are worth knowing before you change one.

A mapping is an allow-list. The output event contains only the fields the mapping produces. Anything in the API response that is not mapped is discarded - so if you remove a row, you stop indexing that field.

Your mapping replaces the default, it does not merge with it. When you open the Mapping tab the table is pre-filled with the input's built-in mapping; saving stores the whole table. Add to it rather than starting from scratch, and use Reset mapping to go back to the shipped version.

Mapping runs first. Every other transform - lookups, replace-fields, parse-JSON, MD5, add-fields, the timestamp field runs after mapping.

A mapping row

Each row in the Mapping tab is one field:

Column

Meaning

Source Field

Where to read the value from in the API response - see Source path syntax.

Type

How to convert the value - see Types.

Destination Field

The field name in the event. A dotted name creates a nested object: error.msg → {"error": {"msg": …}}.

Omit if empty (from version 9.2.0)

When the source has no value, leave the field out of the event instead of writing an empty one. See Empty values.

Rows are applied in order and the last row that produces a value wins, so two rows can write to the same destination - useful for fallbacks. A row whose source is missing writes nothing at all, so it never clobbers an earlier row.

The examples below all run against this response (an SAP CPI message processing log,
abbreviated):

JSON
{
  "MessageGuid": "AGj4nLbR",
  "Status": "FAILED",
  "LogStart": "/Date(1726000000000)/",
  "IntegrationArtifact": { "Id": "OrderReplication", "Name": "Order Replication", "Type": "IFlow" },
  "ErrorInformation": { "ErrorMsg": "Connection refused", "LastErrorModelStepId": "CallActivity_12" },
  "CustomHeaderProperties": [
    { "Name": "orderId", "Value": "4711" },
    { "Name": "region",  "Value": "EU" }
  ],
  "Runs": [
    { "Id": "r1", "OverallState": "COMPLETED", "ChildCount": 3 },
    { "Id": "r2", "OverallState": "FAILED",    "ChildCount": 9 }
  ]
}

Source path syntax

There are four forms of source path. Which one is used is decided by what the path
contains, in this order:

#

Form

Used when the path…

Example

Reads

1

JSON path

contains $

$.Runs[0].Id

"r1"

2

Dot path

contains .

IntegrationArtifact.Name

"Order Replication"

3

Array index

looks like name[0]

Runs[0]

the first run object

4

Field name

anything else

MessageGuid

"AGj4nLbR"

Notes and limits:

  • A plain field name is matched exactly, including case. Spaces are fine -
    Object Type is a valid source field.

  • A dot path walks nested objects only. It cannot index into an array: Runs.0.Id
    and Runs[0].Id both fail. Use a JSON path for that.

  • The name[0] form only works on a top-level array and only with a simple name. For anything deeper use a JSON path: $.IntegrationArtifact.Tags[0].

  • The form is chosen by the characters in the path, so a field whose real name contains
    a . or a $ cannot be addressed at all - the name is read as a path. Such a field has
    to be reached through its parent, e.g. with $.Parent.* or a List row on the parent.

JSON path

Use a JSON path whenever you need to reach into an array - pick an element, pick across all elements, or select an element by one of its values. A path that cannot be parsed, or that points at nothing, yields no value rather than an error, so a mistake shows up as a missing or empty field in the event and not as a failed collection. Always confirm a new path with Test Input.

Supported

Syntax

Meaning

Example

Result

$

the response object

$.Status

"FAILED"

.name

child field

$.IntegrationArtifact.Type

"IFlow"

..name

the field anywhere in the response, at any depth

$..ErrorMsg

"Connection refused"

.*

every child value

$.IntegrationArtifact.*

all three values

[n]

array element by position, 0-based

$.Runs[0].Id

"r1"

[-n]

array element counted from the end

$.Runs[-1].OverallState

"FAILED"

[*]

every array element

$.Runs[*].Id

["r1", "r2"]

[?(…)]

only the elements matching a condition

$.Runs[?(@.OverallState="FAILED")].Id

"r2"

Inside a filter, @ refers to the element being tested. @.Name and @Name both work. A filter also works over an object whose children are objects, not only over an array. The children are tested the same way.

Filter

Meaning

Example

@.field

the field is present

$.Runs[?(@.ChildCount)].Id

= !=

equals / not equals — strings and numbers

$.CustomHeaderProperties[?(@.Name="orderId")].Value

< <= > >=

numeric comparison

$.Runs[?(@.ChildCount>5)].Id → "r2"

&& \\|\\|

and / or

$.Runs[?(@.ChildCount>1 && @.OverallState="FAILED")].Id

Not supported

Syntax

Instead

'single quotes'

use double quotes: [?(@.Name="orderId")]

$['Field Name']

not available - a field whose name contains a space or a : cannot be named in a JSON path. At the top level, map it with a plain field name; nested, map its parent as List or Map and keep the children as they are.

[0,2], [1:3]

pick elements one row at a time, or take them all with [*]

[(@.length-1)], functions

use [-1] for the last element

< > against a quoted value (@.Dur>"10")

these comparisons only work on numeric JSON values against an unquoted number; a quoted comparison yields no value

Filters and wildcards return a list

[*], [?(…)] and .. always produce a list of matches, even when exactly one
element matches:

Source

Type

Result

$.Runs[*].Id

String

"r1, r2" — the list joined with ,

$.Runs[*].Id

List

["r1", "r2"]

$.CustomHeaderProperties[?(@.Name="orderId")].Value

String

"4711"

$.CustomHeaderProperties[?(@.Name="orderId")].Value

anything else

["4711"] — a single-element array

So: use String when the match is a single value you want as a scalar, and List when you genuinely want an array. With any other type a filtered value arrives in the index wrapped in an array, which is rarely what you want.

Types

Type

What it does

String

A list is joined into one string with , . Other values pass through.

Date

Parses a timestamp - see what Date accepts below.

EpochMillis

Epoch milliseconds (number or numeric string) → timestamp.

EpochNanos

Epoch nanoseconds (number or numeric string) → timestamp.

List

Keeps an array as an array, and enables nested mappings.

Json

Parses a string of JSON into structure. If it is not valid JSON the original string is kept. Not offered in the UI dropdown until version 9.3.0 - see Parse a JSON string field.

Boolean, Integer, Long, Map

No conversion - the value is passed through exactly as the API returned it. These are labels for readers of the mapping, not coercions.

Two places care about the type beyond conversion:

  • Timestamp. The Timestamp tab picks the field that becomes the event time, and offers @current_time plus the Date fields of the input's built-in mapping. A newDate field you add under a name of your own will not appear in that list. To use one as the event time, set it over the API, or write your value to the destination name the input already uses for its timestamp.

  • List. Only List triggers the nested mapping described next.

What Date accepts

Value

Read as

Example

A number above 2,147,483,647

epoch milliseconds

1726000000000 → 2024-09-10T20:26:40Z

A smaller number (a 32-bit integer)

epoch seconds

1726000000 → 2024-09-10T20:26:40Z

A string containing 10 or more consecutive digits

those digits as epoch milliseconds

/Date(1726000000000)/, /Date(1726000000000+0000)/

A UTC instant string ending in Z

as written

2026-09-10T12:00:00Z, 2026-09-10T12:00:00.123Z

Anything else

not converted - kept as the original string

2026-09-10, 2026-09-10T12:00:00, 2026-09-10T12:00:00+02:00, 10/09/2026

Two consequences to watch for:

  • A date without a Z is passed through as text, so it will not be a usable timestamp
    in the index. There is no format option in the mapping; either let the input's own handling deal with it, or map it as String and accept that it is text.

  • A string of digits is always read as milliseconds. An API that returns epoch seconds as a string therefore lands in 1970. It needs a numeric field, orEpochMillis/EpochNanos where the unit matches.

Nested objects and arrays

A row with type List keeps the nested structure. What happens to the fields inside it depends on whether the mapping has a step with the same name as the source field.

Without a matching step the nested objects are passed through untouched, with the
field names SAP returned:

Source Field

Type

Destination Field

Runs

List

runs

JSON
{ "runs": [ { "Id": "r1", "OverallState": "COMPLETED", "ChildCount": 3 }, … ] }

With a matching step each element is mapped by that step, so the inner fields get
renamed and typed too. In the UI each step is a tab inside the Mapping tab; the first tab
is the top-level step and the others are named after the source field they map:

Tab MessageProcessingLog (top level)

Source Field

Type

Destination Field

MessageGuid

String

message_guid

Runs

List

runs

Tab Runs

Source Field

Type

Destination Field

Id

String

id

OverallState

String

state

JSON
{ "message_guid": "AGj4nLbR",
  "runs": [ { "id": "r1", "state": "COMPLETED" }, { "id": "r2", "state": "FAILED" } ] }

Steps can nest further - a List row inside the Runs step can point at a Steps step - and a step also works when the source is a single object rather than an array.

Two restrictions:

  • A step is found by the List row's source name, so that source has to be a plain
    field name (or dot path). A JSON path such as $.Runs[*] does not resolve to a step
    named Runs; the elements are passed through unmapped.

  • Inside a step, the source fields are the raw SAP names of the nested object, not the
    destination names of the level above.

Empty values

Omit if empty (version 9.2.0 onwards) (skip-empty) controls what happens when a source yields nothing usable: an empty list - which is what a JSON path filter returns when nothing matches - or
a blank string.

Without it, an unmatched filter still writes a field, and with type String that field is an empty string:

Source Field

Type

Destination Field

Omit if empty

Event

$.CustomHeaderProperties[?(@.Name="invoiceId")].Value

String

invoice_id

✗

{"invoice_id": ""}

$.CustomHeaderProperties[?(@.Name="invoiceId")].Value

String

invoice_id

✓

{}

Tick it for any field that is only present on some events, and always tick it when a filtered row is a fallback for an earlier row - otherwise the empty result overwrites the good value. A source field that is simply absent is skipped either way; the flag is about values that are present but empty.

Use cases

Promote a custom header to a top-level field

The one most customers want. CPI custom header properties arrive as a list of name/value pairs, which is awkward to search. Pull the one you care about out into its own field:

Source Field

Type

Destination Field

Omit if empty

$.CustomHeaderProperties[?(@.Name="orderId")].Value

String

order_id

✓

JSON
{ "order_id": "4711" }

Add one row per header you want promoted. Keep the original CustomHeaderProperties row
as well if you still want the full list indexed.

Use a business key as the trace/correlation id, with a fallback

Two rows to the same destination: the technical id first, the business key second so it wins when present. Omit if empty on the second row is what makes the fallback hold.

Source Field

Type

Destination Field

Omit if empty

MessageGuid

String

trace_id

✗

$.CustomHeaderProperties[?(@.Name="orderId")].Value

String

trace_id

✓

With the header present: {"trace_id": "4711"}. Without it: {"trace_id": "AGj4nLbR"}.

Flatten a nested object

Source Field

Type

Destination Field

IntegrationArtifact.Name

String

integration_artifact_name

ErrorInformation.ErrorMsg

String

error_msg

Match your existing index's field names

Only the Destination Field column changes - point the same sources at the names your dashboards and correlation rules already use. A dotted destination builds the nesting a target platform might expect:

Source Field

Type

Destination Field

ErrorInformation.ErrorMsg

String

error.message

JSON
{ "error": { "message": "Connection refused" } }

Summarise a list into one searchable field

Source Field

Type

Destination Field

$.Runs[*].Id

String

run_ids

JSON
{ "run_ids": "r1, r2" }

Flag events by a condition in a nested list

Record which elements met a condition, not just that some did:

Source Field

Type

Destination Field

Omit if empty

$.Runs[?(@.OverallState="FAILED")].Id

String

failed_run_ids

✓

$.Runs[?(@.ChildCount>5 && @.OverallState="FAILED")].Id

String

long_failed_run_ids

✓

Parse a JSON string field

Some SAP APIs return a details field containing JSON as text. Parsed into real structure,
its keys become searchable fields instead of one long string; text that is not valid JSON
is kept as-is rather than dropped.

In the UI, map the field normally and add parse-json for the destination name on the
Transforms tab:

Source Field

Type

Destination Field

DETAILS

String

details

Over the API the mapping can do it in one step, with type Json:

JSON
{ "src": "DETAILS", "type": "Json", "dst": "details" }

Pick a value out of a deeply nested response

When the value is always called the same thing but its position varies, recursive descent saves you from spelling out the path:

Source Field

Type

Destination Field

Omit if empty

$..ErrorMsg

String

error_msg

✓

Use it deliberately: it matches the field at every depth, so if more than one object in the response has it, a String result is all of them joined together.

Reduce event size

Delete the rows you do not need - large payload and attachment fields are the usual candidates. Nothing you remove is indexed, which is the cheapest way to cut volume.

Testing a mapping

Use the Test Input tab on the input. It runs a real collection and applies the full transform chain -mapping included - returning the first few events exactly as they would be indexed. This is the fastest way to confirm a JSON path matches, and the only reliable way to check a filter against real data.

If a field is missing from the test output:

Symptom

Likely cause

Field absent entirely

The source path matched nothing - check spelling and case, both are exact.

Field is ""

A filter matched nothing; tick Omit if empty if the field is optional.

Value is ["x"] when you wanted x

A filter or wildcard row with a type other than String - see Filters and wildcards return a list.

Inner field names are still SAP's

A List row with no step of the same name, or a step whose rows do not match the raw field names.

A field you did not touch disappeared

Its row was removed from the mapping - a mapping is an allow-list. Use Reset mapping to restore the default.

Timestamps all equal collection time

The timestamp field is @current_time, or the Date field it names is not in the mapping.