# How to Use TreeMap Storage in GenLayer Contracts: Declaration, Nested Maps, and JSON Workarounds

> Learn how to use TreeMap storage in GenLayer contracts for deterministic key-value persistence. Explore declaration, nested maps, and JSON workarounds effectively.

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

---

**TreeMap storage in GenLayer contracts is a deterministic, persistent key-value container declared with generic syntax `TreeMap[KeyType, ValueType]` and managed through lazy initialization, direct assignment, and safe read helpers.**

GenLayer contracts store mutable data in deterministic containers that the GenVM can serialize and replay across nodes. The primary mapping primitive for this state is **TreeMap**, which acts like a persistent dictionary with strict type requirements. This guide explains the exact patterns found in the `genlayerlabs/genlayer-project-boilerplate` repository for declaring, writing to, and reading from `TreeMap` storage in real-world contracts.

## What Is TreeMap Storage in GenLayer?

TreeMap is the canonical key-value store for on-chain state in GenLayer. It guarantees deterministic ordering, ensuring every node produces an identical state hash during execution. This property supports efficient Merkle-based verification and proof generation.

The GenVM linter explicitly rejects native Python `dict` and `list` types for contract storage. According to the boilerplate's [`README.md`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/README.md), using plain Python collections triggers a lint error under the invalid storage types rules. TreeMap is the accepted abstraction for mapping hashable keys to valid storage values.

## Declaring TreeMap Storage in a Contract

You declare a TreeMap field at the class level using generic bracket syntax: `TreeMap[KeyType, ValueType]`. Keys must be hashable SDK types such as `Address`, `str`, or `int`. Values can be any valid storage type, including nested `TreeMap`, `DynArray`, `u256`, or an `@allow_storage` dataclass.

In [`contracts/football_bets.py`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/contracts/football_bets.py), the contract stores user bets with a nested map structure:

```python

# contracts/football_bets.py (lines 23-24)

bets: TreeMap[Address, TreeMap[str, Bet]]
points: TreeMap[Address, u256]

```

This pattern maps each `Address` to an inner `TreeMap` that holds `Bet` objects keyed by a `str` identifier.

## Initializing and Writing to TreeMap Storage

### Lazy Instantiation with get_or_insert_default

TreeMap objects are lazily instantiated. You do not need an explicit constructor call to create them. When you need to ensure a nested map exists before writing, use `get_or_insert_default(key)`.

In [`contracts/football_bets.py`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/contracts/football_bets.py) (lines 87-88), the contract initializes a user's bet map the first time they create a bet:

```python

# contracts/football_bets.py (lines 87-88)

self.bets.get_or_insert_default(gl.message.sender_address)[bet_id] = bet

```

This single expression guarantees the outer map contains an empty `TreeMap[str, Bet]` for the sender before assigning the bet.

### Direct Assignment

For simple updates, assign directly using bracket notation. Because the GenVM tracks every state change, each assignment persists on-chain automatically.

```python
self.profiles[addr] = Profile(name=name, age=age)

```

## Reading from TreeMap Storage

You can read values using standard bracket syntax or the `get` helper. The bracket syntax raises an error if the key is absent, while `get` returns `None` for missing keys.

```python

# Returns None if the address has no profile

profile = self.profiles.get(Address(addr))

# Direct lookup; ensure the key exists or handle exceptions

bet = self.bets[Address(bettor)][bet_id]

```

## Advanced Pattern: Storing Lists Inside a TreeMap

GenLayer does not allow native Python `list` objects inside TreeMap values. The [`contracts/PatternTest.py`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/contracts/PatternTest.py) file demonstrates a workaround at lines 101-114: serialize the list to a JSON string before storage.

```python
import json

@gl.public.write
def add_item(self, key: str, item: str) -> str:
    # Retrieve the JSON-encoded list, defaulting to empty

    lst = json.loads(self.index.get(key) or "[]")
    lst.append(item)
    # Store back as a JSON string

    self.index[key] = json.dumps(lst)
    return self.index[key]

@gl.public.view
def get_items(self, key: str) -> list:
    return json.loads(self.index.get(key) or "[]")

```

This pattern lets you store variable-length collections inside a `TreeMap[str, str]` while remaining fully compliant with GenVM storage rules.

## Complete Example: Profiles and Nested Bets

The following contract combines simple and nested TreeMap patterns with `@allow_storage` dataclasses:

```python

# contracts/example.py

from genlayer import *

@allow_storage
@dataclass
class Profile:
    name: str
    age: u256

class ExampleContract(gl.Contract):
    # Simple map: address → Profile

    profiles: TreeMap[Address, Profile]

    # Nested map: address → (bet_id → Bet)

    bets: TreeMap[Address, TreeMap[str, Bet]]

    def __init__(self):
        # No explicit constructor needed; TreeMap is created on first use

        pass

    @gl.public.write
    def set_profile(self, name: str, age: u256):
        addr = gl.message.sender_address
        self.profiles[addr] = Profile(name=name, age=age)

    @gl.public.view
    def get_profile(self, addr: str) -> dict:
        # Return a plain dict for easier consumption by front-end

        profile = self.profiles.get(Address(addr))
        if not profile:
            return {}
        return {"name": profile.name, "age": int(profile.age)}

    @gl.public.write
    def add_bet(self, bet_id: str, bet: Bet):
        # Ensure a nested TreeMap exists for the sender

        self.bets.get_or_insert_default(gl.message.sender_address)[bet_id] = bet

    @gl.public.view
    def get_bet(self, bettor: str, bet_id: str) -> dict:
        bet = self.bets[Address(bettor)][bet_id]
        return {
            "id": bet.id,
            "team1": bet.team1,
            "team2": bet.team2,
            "predicted_winner": bet.predicted_winner,
        }

```

## Why TreeMap Is Required Over Native Python Collections

- **Deterministic ordering** guarantees that the same state hash is produced on every node.
- **Merkle efficiency** supports proof-generation for on-chain verification.
- **Linter compliance** ensures the GenVM accepts your contract; native Python dictionaries and lists cause compile-time errors as documented in the repository's [`README.md`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/README.md).

## Summary

- Declare `TreeMap[KeyType, ValueType]` at the class level for any persistent key-value state.
- Initialize nested maps lazily with `get_or_insert_default(key)` to avoid missing-key errors.
- Read safely with `get()` for optional keys, or use bracket notation when the key must exist.
- Store complex collections by serializing them to strings (e.g., JSON) when native types are unsupported.
- Avoid native Python `dict` and `list` in storage fields; the GenVM linter rejects them.

## Frequently Asked Questions

### What types can I use as TreeMap keys in GenLayer?

TreeMap keys must be hashable types supported by the GenLayer SDK. Common choices include `Address`, `str`, and `int`. Values can be any valid storage type such as `u256`, `DynArray`, nested `TreeMap`, or an `@allow_storage` dataclass.

### How do I initialize a nested TreeMap for a new user?

Call `get_or_insert_default(key)` on the outer TreeMap. As shown in [`contracts/football_bets.py`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/contracts/football_bets.py) (lines 87-88), this method returns the existing value or creates a default empty map for that key, allowing immediate chained assignment.

### Can I store a Python list directly inside a TreeMap?

No. Native Python `list` is not a valid GenVM storage type. As implemented in [`contracts/PatternTest.py`](https://github.com/genlayerlabs/genlayer-project-boilerplate/blob/main/contracts/PatternTest.py) (lines 101-114), you can JSON-serialize the list into a `str` value, store it in a `TreeMap[str, str]`, and deserialize it on retrieval.

### Does TreeMap require an explicit constructor in GenLayer contracts?

No. TreeMap fields are lazily instantiated on first access. You can declare them at the class level without initialization logic inside `__init__`, and they will be created automatically when you first read from or write to them.