Object and Array Methods

Last updated: 13 minutes read.

Bloblang, the query and transformation language used within Tyk Streams, offers methods for manipulating objects and arrays. These methods provide efficient ways to filter, transform, and manipulate structured data, enabling streamlined data processing pipelines. Whether you’re working with JSON objects or arrays of varying complexity, Bloblang’s Object and Array Methods offer a range of use cases for data validation and transformations.

all

Checks each element of an array against a query and returns true if all elements passed. An error occurs if the target is not an array, or if any element results in the provided query returning a non-boolean result. Returns false if the target array is empty.

Parameters

test <query expression> A test query to apply to each element.

Examples

root.all_over_21 = this.patrons.all(patron -> patron.age >= 21)

# In:  {"patrons":[{"id":"1","age":18},{"id":"2","age":23}]}
# Out: {"all_over_21":false}

# In:  {"patrons":[{"id":"1","age":45},{"id":"2","age":23}]}
# Out: {"all_over_21":true}

any

Checks the elements of an array against a query and returns true if any element passes. An error occurs if the target is not an array, or if an element results in the provided query returning a non-boolean result. Returns false if the target array is empty.

Parameters

test <query expression> A test query to apply to each element.

Examples

root.any_over_21 = this.patrons.any(patron -> patron.age >= 21)

# In:  {"patrons":[{"id":"1","age":18},{"id":"2","age":23}]}
# Out: {"any_over_21":true}

# In:  {"patrons":[{"id":"1","age":10},{"id":"2","age":12}]}
# Out: {"any_over_21":false}

append

Returns an array with new elements appended to the end.

Examples

root.foo = this.foo.append("and", "this")

# In:  {"foo":["bar","baz"]}
# Out: {"foo":["bar","baz","and","this"]}

assign

Merge a source object into an existing destination object. When a collision is found within the merged structures (both a source and destination object contain the same non-object keys) the value in the destination object will be overwritten by that of source object. In order to preserve both values on collision use the merge method.

Parameters

with <unknown> A value to merge the target value with.

Examples

root = this.foo.assign(this.bar)

# In:  {"foo":{"first_name":"fooer","likes":"bars"},"bar":{"second_name":"barer","likes":"foos"}}
# Out: {"first_name":"fooer","likes":"foos","second_name":"barer"}

collapse

Collapse an array or object into an object of key/value pairs for each field, where the key is the full path of the structured field in dot path notation. Empty arrays an objects are ignored by default.

Parameters

include_empty <bool, default false> Whether to include empty objects and arrays in the resulting object.

Examples

root.result = this.collapse()

# In:  {"foo":[{"bar":"1"},{"bar":{}},{"bar":"2"},{"bar":[]}]}
# Out: {"result":{"foo.0.bar":"1","foo.2.bar":"2"}}

An optional boolean parameter can be set to true in order to include empty objects and arrays.

root.result = this.collapse(include_empty: true)

# In:  {"foo":[{"bar":"1"},{"bar":{}},{"bar":"2"},{"bar":[]}]}
# Out: {"result":{"foo.0.bar":"1","foo.1.bar":{},"foo.2.bar":"2","foo.3.bar":[]}}

concat

Concatenates an array value with one or more argument arrays.

Examples

root.foo = this.foo.concat(this.bar, this.baz)

# In:  {"foo":["a","b"],"bar":["c"],"baz":["d","e","f"]}
# Out: {"foo":["a","b","c","d","e","f"]}

contains

Checks whether an array contains an element matching the argument, or an object contains a value matching the argument, and returns a boolean result. Numerical comparisons are made irrespective of the representation type (float versus integer).

Parameters

value <unknown> A value to test against elements of the target.

Examples

root.has_foo = this.thing.contains("foo")

# In:  {"thing":["this","foo","that"]}
# Out: {"has_foo":true}

# In:  {"thing":["this","bar","that"]}
# Out: {"has_foo":false}
root.has_bar = this.thing.contains(20)

# In:  {"thing":[10.3,20.0,"huh",3]}
# Out: {"has_bar":true}

# In:  {"thing":[2,3,40,67]}
# Out: {"has_bar":false}

diff

Create a diff by comparing the current value with the given one. Wraps the github.com/r3labs/diff/v3 package. See its docs for more information.

Parameters

other <unknown> The value to compare against.

enumerated

Converts an array into a new array of objects, where each object has a field index containing the index of the element and a field value containing the original value of the element.

Examples

root.foo = this.foo.enumerated()

# In:  {"foo":["bar","baz"]}
# Out: {"foo":[{"index":0,"value":"bar"},{"index":1,"value":"baz"}]}

explode

Explodes an array or object at a field path.

Parameters

path <string> A dot path to a field to explode.

Examples

On arrays

Exploding arrays results in an array containing elements matching the original document, where the target field of each element is an element of the exploded array:

root = this.explode("value")

# In:  {"id":1,"value":["foo","bar","baz"]}
# Out: [{"id":1,"value":"foo"},{"id":1,"value":"bar"},{"id":1,"value":"baz"}]
On objects

Exploding objects results in an object where the keys match the target object, and the values match the original document but with the target field replaced by the exploded value:

root = this.explode("value")

# In:  {"id":1,"value":{"foo":2,"bar":[3,4],"baz":{"bev":5}}}
# Out: {"bar":{"id":1,"value":[3,4]},"baz":{"id":1,"value":{"bev":5}},"foo":{"id":1,"value":2}}

filter

Executes a mapping query argument for each element of an array or key/value pair of an object. If the query returns false the item is removed from the resulting array or object. The item will also be removed if the query returns any non-boolean value.

Parameters

test <query expression> A query to apply to each element, if this query resolves to any value other than a boolean true the element will be removed from the result.

Examples

root.new_nums = this.nums.filter(num -> num > 10)

# In:  {"nums":[3,11,4,17]}
# Out: {"new_nums":[11,17]}
On objects

When filtering objects the mapping query argument is provided a context with a field key containing the value key, and a field value containing the value.

root.new_dict = this.dict.filter(item -> item.value.contains("foo"))

# In:  {"dict":{"first":"hello foo","second":"world","third":"this foo is great"}}
# Out: {"new_dict":{"first":"hello foo","third":"this foo is great"}}

find

Returns the index of the first occurrence of a value in an array. -1 is returned if there are no matches. Numerical comparisons are made irrespective of the representation type (float versus integer).

Parameters

value <unknown> A value to find.

Examples

root.index = this.find("bar")

# In:  ["foo", "bar", "baz"]
# Out: {"index":1}
root.index = this.things.find(this.goal)

# In:  {"goal":"bar","things":["foo", "bar", "baz"]}
# Out: {"index":1}

find_all

Returns an array containing the indexes of all occurrences of a value in an array. An empty array is returned if there are no matches. Numerical comparisons are made irrespective of the representation type (float versus integer).

Parameters

value <unknown> A value to find.

Examples

root.index = this.find_all("bar")

# In:  ["foo", "bar", "baz", "bar"]
# Out: {"index":[1,3]}
root.indexes = this.things.find_all(this.goal)

# In:  {"goal":"bar","things":["foo", "bar", "baz", "bar", "buz"]}
# Out: {"indexes":[1,3]}

find_all_by

Returns an array containing the indexes of all occurrences of an array where the provided query resolves to a boolean true. An empty array is returned if there are no matches. Numerical comparisons are made irrespective of the representation type (float versus integer).

Parameters

query <query expression> A query to execute for each element.

Examples

root.index = this.find_all_by(v -> v != "bar")

# In:  ["foo", "bar", "baz"]
# Out: {"index":[0,2]}

find_by

Returns the index of the first occurrence of an array where the provided query resolves to a boolean true. -1 is returned if there are no matches.

Parameters

query <query expression> A query to execute for each element.

Examples

root.index = this.find_by(v -> v != "bar")

# In:  ["foo", "bar", "baz"]
# Out: {"index":0}

flatten

Iterates an array and any element that is itself an array is removed and has its elements inserted directly in the resulting array.

Examples

root.result = this.flatten()

# In:  ["foo",["bar","baz"],"buz"]
# Out: {"result":["foo","bar","baz","buz"]}

fold

Takes two arguments: an initial value, and a mapping query. For each element of an array the mapping context is an object with two fields tally and value, where tally contains the current accumulated value and value is the value of the current element. The mapping must return the result of adding the value to the tally.

The first argument is the value that tally will have on the first call.

Parameters

initial <unknown> The initial value to start the fold with. For example, an empty object {}, a zero count 0, or an empty string "".
query <query expression> A query to apply for each element. The query is provided an object with two fields; tally containing the current tally, and value containing the value of the current element. The query should result in a new tally to be passed to the next element query.

Examples

root.sum = this.foo.fold(0, item -> item.tally + item.value)

# In:  {"foo":[3,8,11]}
# Out: {"sum":22}
root.result = this.foo.fold("", item -> "%v%v".format(item.tally, item.value))

# In:  {"foo":["hello ", "world"]}
# Out: {"result":"hello world"}

You can use fold to merge an array of objects together:

root.smoothie = this.fruits.fold({}, item -> item.tally.merge(item.value))

# In:  {"fruits":[{"apple":5},{"banana":3},{"orange":8}]}
# Out: {"smoothie":{"apple":5,"banana":3,"orange":8}}

get

Extract a field value, identified via a dot path, from an object.

Parameters

path <string> A dot path identifying a field to obtain.

Examples

root.result = this.foo.get(this.target)

# In:  {"foo":{"bar":"from bar","baz":"from baz"},"target":"bar"}
# Out: {"result":"from bar"}

# In:  {"foo":{"bar":"from bar","baz":"from baz"},"target":"baz"}
# Out: {"result":"from baz"}

index

Extract an element from an array by an index. The index can be negative, and if so the element will be selected from the end counting backwards starting from -1. E.g. an index of -1 returns the last element, an index of -2 returns the element before the last, and so on.

Parameters

index <integer> The index to obtain from an array.

Examples

root.last_name = this.names.index(-1)

# In:  {"names":["rachel","stevens"]}
# Out: {"last_name":"stevens"}

It is also possible to use this method on byte arrays, in which case the selected element will be returned as an integer.

root.last_byte = this.name.bytes().index(-1)

# In:  {"name":"foobar bazson"}
# Out: {"last_byte":110}

join

Join an array of strings with an optional delimiter into a single string.

Parameters

delimiter <(optional) string> An optional delimiter to add between each string.

Examples

root.joined_words = this.words.join()
root.joined_numbers = this.numbers.map_each(this.string()).join(",")

# In:  {"words":["hello","world"],"numbers":[3,8,11]}
# Out: {"joined_numbers":"3,8,11","joined_words":"helloworld"}

json_path

Executes the given JSONPath expression on an object or array and returns the result. For more complex logic, you can use Gval expressions.

Parameters

expression <string> The JSONPath expression to execute.

Examples

root.all_names = this.json_path("$..name")

# In:  {"name":"alice","foo":{"name":"bob"}}
# Out: {"all_names":["alice","bob"]}

# In:  {"thing":["this","bar",{"name":"alice"}]}
# Out: {"all_names":["alice"]}
root.text_objects = this.json_path("$.body[?(@.type=='text')]")

# In:  {"body":[{"type":"image","id":"foo"},{"type":"text","id":"bar"}]}
# Out: {"text_objects":[{"id":"bar","type":"text"}]}

json_schema

Checks a JSON schema against a value and returns the value if it matches or throws and error if it does not.

Parameters

schema <string> The schema to check values against.

Examples

root = this.json_schema("""{
  "type":"object",
  "properties":{
    "foo":{
      "type":"string"
    }
  }
}""")

# In:  {"foo":"bar"}
# Out: {"foo":"bar"}

# In:  {"foo":5}
# Out: Error("failed assignment (line 1): field `this`: foo invalid type. expected: string, given: integer")

In order to load a schema from a file use the file function.

root = this.json_schema(file(env("ENV_TEST_BLOBLANG_SCHEMA_FILE")))

key_values

Returns the key/value pairs of an object as an array, where each element is an object with a key field and a value field. The order of the resulting array will be random.

Examples

root.foo_key_values = this.foo.key_values().sort_by(pair -> pair.key)

# In:  {"foo":{"bar":1,"baz":2}}
# Out: {"foo_key_values":[{"key":"bar","value":1},{"key":"baz","value":2}]}

keys

Returns the keys of an object as an array.

Examples

root.foo_keys = this.foo.keys()

# In:  {"foo":{"bar":1,"baz":2}}
# Out: {"foo_keys":["bar","baz"]}

length

Returns the length of an array or object (number of keys).

Examples

root.foo_len = this.foo.length()

# In:  {"foo":["first","second"]}
# Out: {"foo_len":2}

# In:  {"foo":{"first":"bar","second":"baz"}}
# Out: {"foo_len":2}

map_each

Parameters

query <query expression> A query that will be used to map each element.

Examples

On arrays

Apply a mapping to each element of an array and replace the element with the result. Within the argument mapping the context is the value of the element being mapped.

root.new_nums = this.nums.map_each(num -> if num < 10 {
  deleted()
} else {
  num - 10
})

# In:  {"nums":[3,11,4,17]}
# Out: {"new_nums":[1,7]}
On objects

Apply a mapping to each value of an object and replace the value with the result. Within the argument mapping the context is an object with a field key containing the value key, and a field value.

root.new_dict = this.dict.map_each(item -> item.value.uppercase())

# In:  {"dict":{"foo":"hello","bar":"world"}}
# Out: {"new_dict":{"bar":"WORLD","foo":"HELLO"}}

map_each_key

Apply a mapping to each key of an object, and replace the key with the result, which must be a string.

Parameters

query <query expression> A query that will be used to map each key.

Examples

root.new_dict = this.dict.map_each_key(key -> key.uppercase())

# In:  {"dict":{"keya":"hello","keyb":"world"}}
# Out: {"new_dict":{"KEYA":"hello","KEYB":"world"}}
root = this.map_each_key(key -> if key.contains("kafka") { "_" + key })

# In:  {"amqp_key":"foo","kafka_key":"bar","kafka_topic":"baz"}
# Out: {"_kafka_key":"bar","_kafka_topic":"baz","amqp_key":"foo"}

merge

Merge a source object into an existing destination object. When a collision is found within the merged structures (both a source and destination object contain the same non-object keys) the result will be an array containing both values, where values that are already arrays will be expanded into the resulting array. In order to simply override destination fields on collision use the assign method.

Parameters

with <unknown> A value to merge the target value with.

Examples

root = this.foo.merge(this.bar)

# In:  {"foo":{"first_name":"fooer","likes":"bars"},"bar":{"second_name":"barer","likes":"foos"}}
# Out: {"first_name":"fooer","likes":["bars","foos"],"second_name":"barer"}

patch

Create a diff by comparing the current value with the given one. Wraps the github.com/r3labs/diff/v3 package. See its docs for more information.

Parameters

changelog <unknown> The changelog to apply.

slice

Extract a slice from an array by specifying two indices, a low and high bound, which selects a half-open range that includes the first element, but excludes the last one. If the second index is omitted then it defaults to the length of the input sequence.

Parameters

low <integer> The low bound, which is the first element of the selection, or if negative selects from the end.
high <(optional) integer> An optional high bound.

Examples

root.beginning = this.value.slice(0, 2)
root.end = this.value.slice(4)

# In:  {"value":["foo","bar","baz","buz","bev"]}
# Out: {"beginning":["foo","bar"],"end":["bev"]}

A negative low index can be used, indicating an offset from the end of the sequence. If the low index is greater than the length of the sequence then an empty result is returned.

root.last_chunk = this.value.slice(-2)
root.the_rest = this.value.slice(0, -2)

# In:  {"value":["foo","bar","baz","buz","bev"]}
# Out: {"last_chunk":["buz","bev"],"the_rest":["foo","bar","baz"]}

sort

Attempts to sort the values of an array in increasing order. The type of all values must match in order for the ordering to succeed. Supports string and number values.

Parameters

compare <(optional) query expression> An optional query that should explicitly compare elements left and right and provide a boolean result.

Examples

root.sorted = this.foo.sort()

# In:  {"foo":["bbb","ccc","aaa"]}
# Out: {"sorted":["aaa","bbb","ccc"]}

It’s also possible to specify a mapping argument, which is provided an object context with fields left and right, the mapping must return a boolean indicating whether the left value is less than right. This allows you to sort arrays containing non-string or non-number values.

root.sorted = this.foo.sort(item -> item.left.v < item.right.v)

# In:  {"foo":[{"id":"foo","v":"bbb"},{"id":"bar","v":"ccc"},{"id":"baz","v":"aaa"}]}
# Out: {"sorted":[{"id":"baz","v":"aaa"},{"id":"foo","v":"bbb"},{"id":"bar","v":"ccc"}]}

sort_by

Attempts to sort the elements of an array, in increasing order, by a value emitted by an argument query applied to each element. The type of all values must match in order for the ordering to succeed. Supports string and number values.

Parameters

query <query expression> A query to apply to each element that yields a value used for sorting.

Examples

root.sorted = this.foo.sort_by(ele -> ele.id)

# In:  {"foo":[{"id":"bbb","message":"bar"},{"id":"aaa","message":"foo"},{"id":"ccc","message":"baz"}]}
# Out: {"sorted":[{"id":"aaa","message":"foo"},{"id":"bbb","message":"bar"},{"id":"ccc","message":"baz"}]}

squash

Squashes an array of objects into a single object, where key collisions result in the values being merged (following similar rules as the .merge() method)

Examples

root.locations = this.locations.map_each(loc -> {loc.state: [loc.name]}).squash()

# In:  {"locations":[{"name":"Seattle","state":"WA"},{"name":"New York","state":"NY"},{"name":"Bellevue","state":"WA"},{"name":"Olympia","state":"WA"}]}
# Out: {"locations":{"NY":["New York"],"WA":["Seattle","Bellevue","Olympia"]}}

sum

Sum the numerical values of an array.

Examples

root.sum = this.foo.sum()

# In:  {"foo":[3,8,4]}
# Out: {"sum":15}

unique

Attempts to remove duplicate values from an array. The array may contain a combination of different value types, but numbers and strings are checked separately ("5" is a different element to 5).

Parameters

emit <(optional) query expression> An optional query that can be used in order to yield a value for each element to determine uniqueness.

Examples

root.uniques = this.foo.unique()

# In:  {"foo":["a","b","a","c"]}
# Out: {"uniques":["a","b","c"]}

values

Returns the values of an object as an array. The order of the resulting array will be random.

Examples

root.foo_vals = this.foo.values().sort()

# In:  {"foo":{"bar":1,"baz":2}}
# Out: {"foo_vals":[1,2]}

with

Returns an object where all but one or more field path arguments are removed. Each path specifies a specific field to be retained from the input object, allowing for nested fields.

If a key within a nested path does not exist then it is ignored.

Examples

root = this.with("inner.a","inner.c","d")

# In:  {"inner":{"a":"first","b":"second","c":"third"},"d":"fourth","e":"fifth"}
# Out: {"d":"fourth","inner":{"a":"first","c":"third"}}

without

Returns an object where one or more field path arguments are removed. Each path specifies a specific field to be deleted from the input object, allowing for nested fields.

If a key within a nested path does not exist or is not an object then it is not removed.

Examples

root = this.without("inner.a","inner.c","d")

# In:  {"inner":{"a":"first","b":"second","c":"third"},"d":"fourth","e":"fifth"}
# Out: {"e":"fifth","inner":{"b":"second"}}

zip

Zip an array value with one or more argument arrays. Each array must match in length.

Examples

root.foo = this.foo.zip(this.bar, this.baz)

# In:  {"foo":["a","b","c"],"bar":[1,2,3],"baz":[4,5,6]}
# Out: {"foo":[["a",1,4],["b",2,5],["c",3,6]]}