# How to Use Nested TreeMap Structures in GenLayer: A Complete Guide

> Master nested TreeMap structures in GenLayer. Learn to use nested TreeMaps or JSON serialization for complex state management in this comprehensive guide. Optimize your contracts today.

- Repository: [GenLayer Labs/genlayer-project-boilerplate](https://github.com/genlayerlabs/genlayer-project-boilerplate)
- Tags: how-to-guide
- Published: 2026-08-20

---

**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`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/contracts/football_bets.py) demonstrates this pattern:

```python
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:

```python

# 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`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/contracts/PatternTest.py) (Pattern 7) demonstrates this workaround:

```python
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:

```python
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:

```python
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`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/tests/direct/test_patterns.py) validates both approaches. For JSON storage, verify both raw string and deserialized forms:

```python
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`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/contracts/football_bets.py) | Two-level `TreeMap[Address, TreeMap[str, Bet]]` |
| [`contracts/PatternTest.py`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/contracts/PatternTest.py) | JSON string workaround for list storage |
| [`tests/direct/test_patterns.py`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/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.