Transform Array Key-Value Pairs Into Top-Level Fields

Flatten structured array data into individual fields using the objectArray:eval() with concatArray() and array:drop()

Query

flowchart LR; %%{init: {"flowchart": {"defaultRenderer": "elk"}} }%% repo{{Events}} 1[\Update Field Data\] 2>Augment Data] 3>Augment Data] 4[/Drop Field\] 5[/Drop Field\] result{{Result Set}} repo --> 1 1 --> 2 2 --> 3 3 --> 4 4 --> 5 5 --> result style 1 fill:#ffbf00; style 4 fill:#2ac76d; style 5 fill:#2ac76d; click 4 #examples-objectarray-eval-kvparse-transform-key-value-pairs-4 click 5 #examples-objectarray-eval-kvparse-transform-key-value-pairs-5
logscale
objectArray:eval(array="in[]", asArray="kvpairs[]", function={
  kvpairs:=format("%s=%s", field=[in.key, in.value])
})
| concatArray("kvpairs", separator=" ")
| kvParse(field="_concatArray")
| drop([_concatArray])
| array:drop("kvpairs[]")

Introduction

The objectArray:eval() function can be used to iterate over structured array objects and apply a transformation function to each element. It is particularly useful when events contain arrays of key-value pair objects that need to be reshaped or reformatted before further processing.

In this example, the objectArray:eval() function is used to iterate over an array of key-value pair objects stored in the in[] array field, formatting each pair into a key=value string. The resulting strings are then concatenated into a single space-separated string, parsed with kvParse() to promote each key-value pair into a top-level field, and finally the intermediate working fields are removed using drop() and array:drop().

Example incoming data might look like this:

in[0].keyin[0].valuein[1].keyin[1].valuein[2].keyin[2].value@timestamp
foobarbazquxzapwham2026-03-30T08:15:00Z
alphabravocharliedeltaechofoxtrot2026-03-30T08:16:00Z
red42blue17green992026-03-30T08:17:00Z
pingpongflipflopzigzag2026-03-30T08:18:00Z
axbxcxdxexfx2026-03-30T08:19:00Z

Each event contains an array of objects under the in[] field, where each object has a key and a value sub-field. The goal is to promote these dynamic key-value pairs into individual top-level fields on each event, regardless of what the key names are.

Step-by-Step

  1. Starting with the source repository events.

  2. flowchart LR; %%{init: {"flowchart": {"defaultRenderer": "elk"}} }%% repo{{Events}} 1[\Update Field Data\] 2>Augment Data] 3>Augment Data] 4[/Drop Field\] 5[/Drop Field\] result{{Result Set}} repo --> 1 1 --> 2 2 --> 3 3 --> 4 4 --> 5 5 --> result style 1 fill:#ffbf00; style 4 fill:#2ac76d; style 5 fill:#2ac76d; click 4 #examples-objectarray-eval-kvparse-transform-key-value-pairs-4 click 5 #examples-objectarray-eval-kvparse-transform-key-value-pairs-5 style 1 fill:#ff0000,stroke-width:4px,stroke:#000;
    logscale
    objectArray:eval(array="in[]", asArray="kvpairs[]", function={
      kvpairs:=format("%s=%s", field=[in.key, in.value])
    })

    The objectArray:eval() function iterates over each element in the in[] array, as specified by the array parameter. The inner function block is applied to each element in turn. The asArray parameter specifies that the results of the inner function are collected into a new array named kvpairs[], updating the event with this new array field.

    Inside the function block, the format() function constructs a string in the form key=value for each array element by referencing in.key and in.value — the sub-fields of each object in the in[] array. The result is assigned to kvpairs for each iteration, producing an array of formatted strings such as foo=bar, baz=qux, and zap=wham for the first event.

  3. flowchart LR; %%{init: {"flowchart": {"defaultRenderer": "elk"}} }%% repo{{Events}} 1[\Update Field Data\] 2>Augment Data] 3>Augment Data] 4[/Drop Field\] 5[/Drop Field\] result{{Result Set}} repo --> 1 1 --> 2 2 --> 3 3 --> 4 4 --> 5 5 --> result style 1 fill:#ffbf00; style 4 fill:#2ac76d; style 5 fill:#2ac76d; click 4 #examples-objectarray-eval-kvparse-transform-key-value-pairs-4 click 5 #examples-objectarray-eval-kvparse-transform-key-value-pairs-5 style 2 fill:#ff0000,stroke-width:4px,stroke:#000;
    logscale
    | concatArray("kvpairs", separator=" ")

    The concatArray() function joins all elements of the kvpairs[] array into a single string, using a space character as the separator specified by the separator parameter. The result is returned in a new field named _concatArray, which is the default output field name for this function. For the first event, this produces a string such as foo=bar baz=qux zap=wham.

  4. flowchart LR; %%{init: {"flowchart": {"defaultRenderer": "elk"}} }%% repo{{Events}} 1[\Update Field Data\] 2>Augment Data] 3>Augment Data] 4[/Drop Field\] 5[/Drop Field\] result{{Result Set}} repo --> 1 1 --> 2 2 --> 3 3 --> 4 4 --> 5 5 --> result style 1 fill:#ffbf00; style 4 fill:#2ac76d; style 5 fill:#2ac76d; click 4 #examples-objectarray-eval-kvparse-transform-key-value-pairs-4 click 5 #examples-objectarray-eval-kvparse-transform-key-value-pairs-5 style 3 fill:#ff0000,stroke-width:4px,stroke:#000;
    logscale
    | kvParse(field="_concatArray")

    The kvParse() function parses the space-separated key=value string stored in _concatArray, as specified by the field parameter. Each key-value pair found in the string is promoted to a top-level field on the event. For example, the string foo=bar baz=qux zap=wham results in three new fields: foo with value bar, baz with value qux, and zap with value wham. The default separator used by kvParse() is a space, which matches the output of the previous step.

  5. flowchart LR; %%{init: {"flowchart": {"defaultRenderer": "elk"}} }%% repo{{Events}} 1[\Update Field Data\] 2>Augment Data] 3>Augment Data] 4[/Drop Field\] 5[/Drop Field\] result{{Result Set}} repo --> 1 1 --> 2 2 --> 3 3 --> 4 4 --> 5 5 --> result style 1 fill:#ffbf00; style 4 fill:#2ac76d; style 5 fill:#2ac76d; click 4 #examples-objectarray-eval-kvparse-transform-key-value-pairs-4 click 5 #examples-objectarray-eval-kvparse-transform-key-value-pairs-5 style 4 fill:#ff0000,stroke-width:4px,stroke:#000;
    logscale
    | drop([_concatArray])

    The drop() function removes the intermediate scalar field _concatArray from each event. This field was only needed as an intermediate representation during the parsing step and is no longer required in the final output.

  6. flowchart LR; %%{init: {"flowchart": {"defaultRenderer": "elk"}} }%% repo{{Events}} 1[\Update Field Data\] 2>Augment Data] 3>Augment Data] 4[/Drop Field\] 5[/Drop Field\] result{{Result Set}} repo --> 1 1 --> 2 2 --> 3 3 --> 4 4 --> 5 5 --> result style 1 fill:#ffbf00; style 4 fill:#2ac76d; style 5 fill:#2ac76d; click 4 #examples-objectarray-eval-kvparse-transform-key-value-pairs-4 click 5 #examples-objectarray-eval-kvparse-transform-key-value-pairs-5 style 5 fill:#ff0000,stroke-width:4px,stroke:#000;
    logscale
    | array:drop("kvpairs[]")

    The array:drop() function removes the intermediate array field kvpairs[] from each event. This array was created by objectArray:eval() as a working structure to hold the formatted key=value strings and is no longer needed once parsing is complete. Using array:drop() rather than drop() is required here because kvpairs[] is an array field, and array fields must be removed with the dedicated array:drop() function.

  7. Event Result set.

Summary and Results

The query is used to flatten structured array data — where each array element is an object containing a key and a value sub-field — into individual top-level fields on each event. It achieves this by iterating over the array with objectArray:eval(), formatting each pair as a key=value string, concatenating the strings with concatArray(), parsing the result with kvParse(), and finally cleaning up the intermediate scalar field with drop() and the intermediate array field with array:drop().

This query is useful, for example, to normalize events from systems that emit dynamic or variable sets of attributes as arrays of key-value objects — such as configuration management tools, metadata APIs, or enrichment pipelines — so that the attributes can be used directly in filters, aggregations, and dashboards without requiring knowledge of the specific key names in advance.

Sample output from the incoming example data:

foobazzapalphacharlieechoredbluegreenpingflipzigaxcxex
barquxwham<no value><no value><no value><no value><no value><no value><no value><no value><no value><no value><no value><no value>
<no value><no value><no value>bravodeltafoxtrot<no value><no value><no value><no value><no value><no value><no value><no value><no value>
<no value><no value><no value><no value><no value><no value>421799<no value><no value><no value><no value><no value><no value>
<no value><no value><no value><no value><no value><no value><no value><no value><no value>pongflopzag<no value><no value><no value>
<no value><no value><no value><no value><no value><no value><no value><no value><no value><no value><no value><no value>bxdxfx

Note that each event only contains values for the fields that were present in its own in[] array — fields from other events are absent for that row, since the key names are dynamic and differ between events. The field names in the output are entirely determined by the values of the key sub-fields in the input array, making this pattern flexible for any set of dynamic attribute names.