Nested Data Access
View SourcePredicator supports nested data structure access using both dot notation and bracket notation, letting you reference deeply nested values in your context.
iex> context = %{"user" => %{"age" => 47, "name" => %{"first" => "John", "last" => "Doe"}, "profile" => %{"role" => "admin"}, "settings" => %{"theme" => "dark", "notifications" => true}}, "config" => %{"database" => %{"host" => "localhost", "port" => 5432}}, "items" => ["apple", "banana", "cherry"], "scores" => [85, 92, 78, 96]}
iex> Predicator.evaluate("user.name.first == 'John'", context)
{:ok, true}
iex> context = %{"user" => %{"age" => 47, "name" => %{"first" => "John", "last" => "Doe"}, "profile" => %{"role" => "admin"}, "settings" => %{"theme" => "dark", "notifications" => true}}, "config" => %{"database" => %{"host" => "localhost", "port" => 5432}}, "items" => ["apple", "banana", "cherry"], "scores" => [85, 92, 78, 96]}
iex> Predicator.evaluate("user.age > 18", context)
{:ok, true}
iex> context = %{"user" => %{"age" => 47, "name" => %{"first" => "John", "last" => "Doe"}, "profile" => %{"role" => "admin"}, "settings" => %{"theme" => "dark", "notifications" => true}}, "config" => %{"database" => %{"host" => "localhost", "port" => 5432}}, "items" => ["apple", "banana", "cherry"], "scores" => [85, 92, 78, 96]}
iex> Predicator.evaluate("config.database.port == 5432", context)
{:ok, true}Bracket notation
iex> context = %{"user" => %{"name" => %{"first" => "John"}, "settings" => %{"theme" => "dark"}, "profile" => %{"role" => "admin"}}, "items" => ["apple"], "scores" => [85, 92]}
iex> Predicator.evaluate("user['name']['first'] == 'John'", context)
{:ok, true}
iex> context = %{"user" => %{"name" => %{"first" => "John"}, "settings" => %{"theme" => "dark"}, "profile" => %{"role" => "admin"}}, "items" => ["apple"], "scores" => [85, 92]}
iex> Predicator.evaluate("user['settings']['theme'] == 'dark'", context)
{:ok, true}Array access
iex> context = %{"items" => ["apple", "banana", "cherry"], "scores" => [85, 92, 78, 96]}
iex> Predicator.evaluate("items[0] == 'apple'", context)
{:ok, true}
iex> context = %{"items" => ["apple", "banana", "cherry"], "scores" => [85, 92, 78, 96]}
iex> Predicator.evaluate("scores[1] > 90", context)
{:ok, true}
iex> context = %{"scores" => [85, 92, 78, 96], "index" => 2}
iex> Predicator.evaluate("scores[index] > 80", context)
{:ok, false}Mixed notation
iex> context = %{"user" => %{"settings" => %{"theme" => "dark"}, "profile" => %{"role" => "admin"}}}
iex> Predicator.evaluate("user.settings['theme'] == 'dark'", context)
{:ok, true}
iex> context = %{"user" => %{"settings" => %{"theme" => "dark"}, "profile" => %{"role" => "admin"}}}
iex> Predicator.evaluate("user['profile'].role == 'admin'", context)
{:ok, true}Chained access and combined expressions
iex> context = %{"user" => %{"name" => %{"first" => "John", "last" => "Doe"}, "profile" => %{"role" => "admin"}, "settings" => %{"notifications" => true}}}
iex> Predicator.evaluate("user['name']['first'] + ' ' + user['name']['last']", context)
{:ok, "John Doe"}
iex> context = %{"user" => %{"profile" => %{"role" => "admin"}, "settings" => %{"notifications" => true}}}
iex> Predicator.evaluate("user.profile.role == 'admin' AND user.settings.notifications", context)
{:ok, true}Missing paths
A missing path returns :undefined rather than raising:
iex> context = %{"user" => %{"profile" => %{"role" => "admin"}}}
iex> Predicator.evaluate("user.profile.email == 'test'", context)
{:ok, :undefined}Atom-keyed contexts
Nested access works with atom keys as well as string keys:
iex> atom_context = %{user: %{name: %{first: "Jane"}}}
iex> Predicator.evaluate("user.name.first == 'Jane'", atom_context)
{:ok, true}Nested lists
iex> list_context = %{"user" => %{"hobbies" => ["reading", "coding"]}}
iex> Predicator.evaluate("'coding' in user.hobbies", list_context)
{:ok, true}Summary
- Dot notation:
user.profile.namefor nested object access - Bracket notation:
user['profile']['name']for dynamic key access - Array indexing:
items[0],scores[index]for list access - Mixed styles:
user.settings['theme']combining both notations - Unlimited nesting depth:
app.database.config.settings.ssl - Mixed key types: works with string keys, atom keys, or both
- Graceful fallback: returns
:undefinedfor missing paths or out-of-bounds access - Type preservation: maintains original data types (strings, numbers, booleans, lists)
- Backwards compatible: simple variable names work exactly as before