Structures

Collection of key-value pairs; a data dictionary

A structure is a collection of data where each element of data is addressed by a name or key and it can hold a value of any type. Like a dictionary but on steroids:

// Create a struct via function
myStruct = structnew()

// Create a struct via literal syntax
myStruct = {}

Java Map Interoperability: Any Java Map implementation can be used with BoxLang struct functions! This includes HashMap, LinkedHashMap, TreeMap, ConcurrentHashMap, Properties, and any other class implementing java.util.Map. BoxLang struct BIFs and member functions work seamlessly with Java Maps.

As an analogy, think about a refrigerator. If we’re keeping track of the produce inside the fridge, we don’t really care about where the produce is in, or basically: order doesn’t matter. Instead, we organize things by name, which are unique, and each name can have any value. The name grapes might have the value 2, then the name lemons might have the value 1, and eggplants the value 6.

All BoxLang structures are passed to functions as memory references, not values. Keep that in mind when working with structures. There is also the passby=reference|value attribute to function arguments where you can decide whether to pass by reference or value.

📋 Table of Contents

🔑 Key-Value Pairs

A structure is an unordered collection where the data gets organized as a key and value pair. BoxLang syntax for structures follows the following syntax:

Since BoxLang is a case-insensitive language, the above structure will store all keys in uppercase. If you want the exact casing to be preserved in the structure, then surround the keys with quotes (").

The exact casing is extremely important if you will be converting these structures into JSON in the future.

The key is the address, and the value is the data at that address. Please note that the value can be ANYTHING. It can be an array, an object, a simple value, or even an embedded structure. It doesn't matter.

📥 Retrieving Values

Retrieving values from structures can be done via dot or array notation or the structFind() function. Let's explore these approaches:

🔢 Array Notation

⚫ Dot Notation

structFind( structure, key, [defaultValue ] )

https://boxlang.ortusbooks.com/boxlang-language/reference/built-in-functions/struct/structfind

🛡️ Safe Navigation

BoxLang also supports the concept of safe navigation when dealing with structures. Sometimes it can be problematic when using dot notation on nested structures since some keys might not exist or be null. You can avoid this pain by using the safe navigation operator ?. instead of the traditional . , and combine it with the elvis operator ?: so if null, then returning a value.

📤 Setting Values

I can also set new or override structure values a la carte. You can do so via array/dot notation or via the structInsert(), structUpdate() functions (https://boxlang.ortusbooks.com/boxlang-language/reference/built-in-functions/struct/structinsert, https://boxlang.ortusbooks.com/boxlang-language/reference/built-in-functions/struct/structupdate)

📚 Struct Built-In Functions (BIFs)

BoxLang provides a comprehensive set of struct BIFs organized by functionality. All struct BIFs can be called as member methods on Struct objects.

🔨 Creation & Configuration Functions

Function
Purpose
Example

structNew()

Create new struct

structNew("ordered")[:]

structCopy()

Create shallow copy

structCopy(myStruct)

structToSorted()

Create sorted copy

structToSorted(myStruct, "text")

➕ Modification Functions

Function
Purpose
Example

structInsert()

Insert key-value pair

structInsert(struct, "key", "value")

structUpdate()

Update existing key

structUpdate(struct, "key", "newValue")

structAppend()

Merge structs

structAppend(struct1, struct2)

structDelete()

Remove key

structDelete(struct, "key")

structClear()

Remove all keys

structClear(struct)

🔍 Search & Filter Functions

Function
Purpose
Example

structFind()

Find value by key

structFind(struct, "key") → value

structGet()

Get with dot notation

structGet("struct.nested.key")

structFindKey()

Find keys matching value

structFindKey(struct, "searchValue")

structFindValue()

Find values matching criteria

structFindValue(struct, "pattern")

structKeyExists()

Check if key exists

structKeyExists(struct, "key")true

structFilter()

Filter by condition

structFilter(struct, (k,v) -> v > 5)

structEvery()

Test all entries

structEvery(struct, (k,v) -> v > 0)true

structSome()

Test any entry

structSome(struct, (k,v) -> v > 10)true

structNone()

Test no entries match

structNone(struct, (k,v) -> v < 0)true

🔄 Transformation Functions

Function
Purpose
Example

structMap()

Transform values

structMap(struct, (k,v) -> v * 2)

structReduce()

Reduce to single value

structReduce(struct, (acc,k,v) -> acc + v, 0)

structKeyTranslate()

Translate keys

structKeyTranslate(struct, mapping)

structToQueryString()

Convert to query string

structToQueryString(struct)"key1=val1&key2=val2"

📊 Sorting Functions

Function
Purpose
Example

structSort()

Sort keys to array

structSort(struct, "text")["key1", "key2"]

📏 Information Functions

Function
Purpose
Example

structCount()

Get key count

structCount(struct)5

structIsEmpty()

Check if empty

structIsEmpty(struct)false

structKeyArray()

Get keys as array

structKeyArray(struct)["key1", "key2"]

structKeyList()

Get keys as list

structKeyList(struct)"key1,key2"

structValueArray()

Get values as array

structValueArray(struct)[val1, val2]

structEquals()

Compare structs

structEquals(struct1, struct2)true

structIsCaseSensitive()

Check case sensitivity

structIsCaseSensitive(struct)false

structIsOrdered()

Check if ordered

structIsOrdered(struct)true

structGetMetadata()

Get struct metadata

structGetMetadata(struct)

🔗 Iteration Functions

Function
Purpose
Example

structEach()

Iterate each entry

structEach(struct, (k,v) => println(k & ":" & v))

⚙️ Member Functions

All struct BIFs can be called as member functions on Struct objects for cleaner, more fluent code:

🔧 Java Map Methods

Since BoxLang structs implement the java.util.Map interface, you have access to all Java Map methods:

🎯 BoxLang Struct Native Methods

The BoxLang Struct type provides additional methods beyond standard Java Map operations:

🔑 Key Differences: String Keys vs Key Objects

Operation
String Key
Key Object

Get value

struct.get("name")

struct.get(Key.of("name"))

Check existence

struct.containsKey("name")

struct.containsKey(Key.of("name"))

Set value

struct.put("name", val)

struct.put(Key.of("name"), val)

Remove

struct.remove("name")

struct.remove(Key.of("name"))

Case handling

Always case-insensitive*

Respects struct type

*Unless the struct is case-sensitive type

BoxLang Key Objects: The Key class is a special BoxLang type that simulates a case-insensitive string by default. When working with case-sensitive structs, use KeyCased for exact case matching.

🗂️ Structure Types

In BoxLang, not only can you create case-insensitive unordered structures but also the following types using the structNew() function (https://boxlang.ortusbooks.com/boxlang-language/reference/built-in-functions/struct/structnew)

  • case-sensitive

  • normal

  • ordered or linked

  • ordered-case-sensitive

  • soft

  • synchronized

  • weak

Here is the signature for the structnew() function:

Now let's create some different types of structures

💻 Literal Syntax

You can also use literal syntax for some of these types:

🔁 Looping Over Structures

You can use different constructs for looping over structures:

  • for loops

  • loop constructs

  • each() closures

⚡ Multi-Threaded Looping

BoxLang allows you to leverage the each() operations in a multi-threaded fashion. The structEach() or each() functions allow for a parallel and maxThreads arguments so the iteration can happen concurrently on as many maxThreads as supported by your JVM.

This is incredibly awesome as now your callback will be called concurrently! However, please note that once you enter concurrency land, you should shiver and tremble. Thread concurrency will be of the utmost importance, and you must ensure that var scoping is done correctly and that appropriate locking strategies are in place.

Even though this approach to multi-threaded looping is easy, it is not performant and/or flexible. Under the hood, the BoxLang uses a single thread executor for each execution, do not allow you to deal with exceptions, and if an exception occurs in an element processor, good luck; you will never know about it. This approach can be verbose and error-prone, but it's easy. You also don't control where the processing thread runs and are at the mercy of the engine.

🚀 ColdBox Futures Parallel Programming

If you would like a functional and much more flexible approach to multi-threaded or parallel programming, consider using the ColdBox Futures approach (usable in ANY framework or non-framework code). You can use it by installing ColdBox or WireBox into any BoxLang application and leveraging our async programming constructs, which behind the scenes, leverage the entire Java Concurrency and Completable Futures frameworks.

ColdBox Futures and Async Programming

Here are some methods that will allow you to do parallel computations:

  • all( a1, a2, ... ):Future : This method accepts an infinite amount of future objects, closures, or an array of closures/futures to execute them in parallel. When you call on it, it will return a future that will retrieve an array of the results of all the operations.

  • allApply( items, fn, executor ):array : This function can accept an array of items or a struct of items of any type and apply a function to each of the items in parallel. The fn argument receives the appropriate item and must return a result. Consider this a parallel map() operation.

  • anyOf( a1, a2, ... ):Future : This method accepts an infinite amount of future objects, closures, or an array of closures/futures and will execute them in parallel. However, instead of returning all of the results in an array like all(), this method will return the future that executes the fastest! Race Baby!

  • withTimeout( timeout, timeUnit ) : Apply a timeout to all() or allApply() operations. The timeUnit can be days, hours, microseconds, milliseconds, minutes, nanoseconds, and seconds. The default is milliseconds.

✨ Trailing Commas

BoxLang supports trailing commas when defining array and struct literals. Just in case you miss a dangling comma, we won't shout at you!

👂 Change Listeners

All arrays and structures offer the ability to listen to changes to themselves. This is all done via our $bx metadata object available on all arrays/structures. You will call the registerChangeListener() function to register a closure/lambda that will listen to changes on the struct. You can listen:

  • To all changes in the structure

  • To a specific key in the structure

However, you must EXPLICITLY return the value that will be stored in the change.

The signature of the closure/lambda is the following

Here are a few simple examples:

Here is another example when listening to a specific index:

Last updated

Was this helpful?