How to Use Nested TreeMap Structures in GenLayer: A Complete Guide
GenLayer contracts store state in deterministic TreeMap collections, and you can nest them by using another TreeMap as the value type or by serializing complex data as JSON strings.
GenLayer's deterministic execution environment requires all contract state to use serializable, deterministic types. The TreeMap is the primary mapping structure for key-value storage, and hierarchical data patterns are essential for real-world smart contracts. This guide covers both native nested TreeMap patterns and the JSON serialization workaround for complex nested structures.
Native Nested TreeMap Pattern
When your nested structure consists of deterministic types, you can declare a TreeMap where the value is itself another TreeMap. This is the cleanest and most performant approach.
Two-Level TreeMap Implementation
The FootballBets contract at contracts/football_bets.py demonstrates this pattern:
class FootballBets(gl.Contract):
bets: TreeMap[Address, TreeMap[str, Bet]]
Here, the top-level key is a player Address, and the second-level key is a string bet identifier. The inner TreeMap[str, Bet] stores Bet dataclass instances.
Insertion and Retrieval Operations
Access nested values with double-index operations:
# Insert a new bet
sender = gl.message.sender_address
bet_id = f"{game_date}_{team1}_{team2}".lower()
self.bets.get_or_insert_default(sender)[bet_id] = Bet(
id=bet_id,
has_resolved=False,
game_date=game_date,
resolution_url=match_resolution_url,
team1=team1,
team2=team2,
predicted_winner=predicted_winner,
real_winner="",
real_score="",
)
# Later retrieval
bet = self.bets[sender_address][bet_id]
The get_or_insert_default() method ensures the inner TreeMap exists before insertion, avoiding KeyError exceptions.
When to Use Native Nesting
Use native nested TreeMap structures when:
- The value type is a deterministic object (dataclass,
TreeMap, or primitive) - You need efficient O(log n) lookup at each level
- The nesting depth is limited (two levels work reliably)
JSON String Workaround for Complex Collections
When you need to store lists or deeply nested dict structures that aren't directly supported as storage types, serialize the value as a JSON string.
Why JSON Serialization Is Required
TreeMap values must be deterministic scalar types: str, u256, Address, or other registered deterministic objects. Python list and dict types are not allowed directly because their in-memory representation isn't deterministic across executions. The PatternTest contract at contracts/PatternTest.py (Pattern 7) demonstrates this workaround:
class PatternTest(gl.Contract):
index: TreeMap[str, str] # stores JSON-encoded lists
@gl.public.write
def add_to_index(self, key: str, new_id: str) -> str:
"""Append a new ID to the JSON-encoded list."""
id_list = json.loads(self.index.get(key) or "[]")
id_list.append(new_id)
self.index[key] = json.dumps(id_list, sort_keys=True)
return self.index[key]
@gl.public.view
def get_index(self, key: str) -> list:
"""Read the JSON-encoded list."""
return json.loads(self.index.get(key) or "[]")
Critical Implementation Details
- Always use
sort_keys=Trueinjson.dumps()to guarantee deterministic ordering for consensus - Read-modify-write pattern: Load the JSON string, deserialize, mutate, then serialize and store
- Handle missing keys with
or "[]"to initialize empty collections
PatternTest JSON Operations in Detail
The complete workflow for appending to a per-match index:
def add_to_index(self, key: str, new_id: str) -> str:
# Decode existing list or start empty
ids = json.loads(self.index.get(key) or "[]")
# Mutate in-memory
ids.append(new_id)
# Store back as deterministic JSON string
self.index[key] = json.dumps(ids, sort_keys=True)
return self.index[key]
Retrieval deserializes on every read:
def get_index(self, key: str) -> list:
return json.loads(self.index.get(key) or "[]")
Comparison: Native Nesting vs. JSON Workaround
| Approach | Best For | Performance | Code Complexity |
|---|---|---|---|
Native nested TreeMap |
Deterministic value types (dataclasses, other TreeMaps) | Optimal O(log n) per level | Low—direct access |
| JSON string storage | Lists, arbitrary dicts, dynamic collections | Moderate—serialization overhead | Medium—encode/decode required |
Testing Nested TreeMap Patterns
The test suite at tests/direct/test_patterns.py validates both approaches. For JSON storage, verify both raw string and deserialized forms:
def test_json_string_stored_and_retrieved(direct_deploy):
contract = direct_deploy("contracts/PatternTest.py")
raw = contract.add_to_index("key", "abc")
assert json.loads(raw) == ["abc"]
# Also verify via view function
assert contract.get_index("key") == ["abc"]
Key Source Files in genlayer-project-boilerplate
| File | Pattern Demonstrated |
|---|---|
contracts/football_bets.py |
Two-level TreeMap[Address, TreeMap[str, Bet]] |
contracts/PatternTest.py |
JSON string workaround for list storage |
tests/direct/test_patterns.py |
Unit tests for TestNestedTreeMapWorkaround and related patterns |
Summary
- Native nested
TreeMapstructures work when values are deterministic types—declareTreeMap[K, TreeMap[K2, V]]and useget_or_insert_default()for safe access. - JSON serialization enables storage of lists and complex objects in
TreeMap[str, str]—always usesort_keys=Truefor deterministic output. - Implement read-modify-write atomicity: deserialize, mutate, then serialize and store to prevent partial state corruption.
- Consult
FootballBetsfor production nested mapping patterns andPatternTestfor complex collection workarounds.
Frequently Asked Questions
Can I nest TreeMap more than two levels deep?
Yes, you can nest TreeMap structures arbitrarily deep as long as each value type remains deterministic. For example, TreeMap[str, TreeMap[str, TreeMap[str, int]]] is valid. However, each additional level increases gas costs for traversal, and the JSON workaround becomes preferable for highly dynamic or irregular nesting.
Why can't I store a Python list directly in a TreeMap?
Python list objects are not deterministic—their memory layout and iteration order can vary across Python implementations and versions. The GenVM requires reproducible state serialization for consensus. Converting to a JSON string with sort_keys=True produces a deterministic byte representation that all validators can agree upon.
How do I delete from a nested TreeMap?
For native nesting, use del self.outer_map[key][inner_key] or self.outer_map[key].pop(inner_key, None). For JSON-serialized lists, load the string, remove the element from the deserialized list, then write back: self.index[key] = json.dumps([x for x in ids if x != to_remove], sort_keys=True).
Is there a performance cost to JSON serialization?
Yes—each read requires json.loads() and each write requires json.dumps(). For frequently accessed data with simple structure, prefer native TreeMap nesting. Reserve JSON serialization for cases where the data model inherently requires lists or highly variable schemas.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →