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=True in json.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 TreeMap structures work when values are deterministic types—declare TreeMap[K, TreeMap[K2, V]] and use get_or_insert_default() for safe access.
  • JSON serialization enables storage of lists and complex objects in TreeMap[str, str]—always use sort_keys=True for deterministic output.
  • Implement read-modify-write atomicity: deserialize, mutate, then serialize and store to prevent partial state corruption.
  • Consult FootballBets for production nested mapping patterns and PatternTest for 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →