# zodb-json-codec

> Fast pickle-to-JSON transcoder for ZODB, implemented in Rust via PyO3.

zodb-json-codec converts ZODB pickle records into human-readable, JSONB-queryable JSON
while maintaining full roundtrip fidelity. It is designed as the codec layer for
zodb-pgjsonb, a PostgreSQL JSONB storage backend for ZODB.

## Key Facts

- Package: zodb-json-codec
- License: MIT
- Python: 3.10+
- Rust: 1.70+ (PyO3 0.28)
- Repository: https://github.com/bluedynamics/zodb-json-codec
- PyPI: https://pypi.org/project/zodb-json-codec/

## Architecture

The codec has three decode output paths and two encode input paths:

### Decode (pickle bytes → structured output)

1. **Dict path**: pickle → Decoder → PickleValue AST → pyconv.rs → Python dict
2. **JSON serde path**: pickle → Decoder → PickleValue AST → json.rs → serde_json → JSON string
3. **Direct JSON path**: pickle → Decoder → PickleValue AST → json_writer.rs → JSON string (GIL released)

### Encode (structured input → pickle bytes)

1. **Dict path**: Python dict → pyconv.rs → pickle bytes (direct, bypasses PickleValue)
2. **JSON path**: JSON string → json.rs → PickleValue AST → encode.rs → pickle bytes

### Key modules

- `decode.rs` — Pickle VM: bytes → PickleValue AST (supports protocol 2-3)
- `encode.rs` — PickleValue AST → pickle bytes
- `pyconv.rs` — Direct PickleValue ↔ PyObject + direct PyObject → pickle bytes
- `json.rs` — PickleValue ↔ serde_json::Value
- `json_writer.rs` — Direct PickleValue → JSON string writer (no serde allocation)
- `known_types.rs` — Handlers for datetime, date, time, timedelta, Decimal, UUID, set, frozenset
- `btrees.rs` — BTree state flattening/reconstruction for all BTrees package types
- `zodb.rs` — ZODB two-pickle record format (class + state with shared memo)
- `types.rs` — PickleValue enum (48 bytes, boxed Instance variant)

## Python API

```python
import zodb_json_codec

# ZODB records (two concatenated pickles: class + state)
record = zodb_json_codec.decode_zodb_record(data)
data = zodb_json_codec.encode_zodb_record(record)

# Single-pass decode for PostgreSQL storage
class_mod, class_name, state, refs = zodb_json_codec.decode_zodb_record_for_pg(data)

# Direct JSON string path (GIL released, fastest for PG)
class_mod, class_name, json_str, refs = zodb_json_codec.decode_zodb_record_for_pg_json(data)

# Standalone pickle <-> Python dict
result = zodb_json_codec.pickle_to_dict(pickle_bytes)
pickle_bytes = zodb_json_codec.dict_to_pickle(result)

# Standalone pickle <-> JSON string
json_str = zodb_json_codec.pickle_to_json(pickle_bytes)
pickle_bytes = zodb_json_codec.json_to_pickle(json_str)
```

## JSON Type Markers

The codec uses compact marker keys to represent Python types without direct JSON equivalents:

| Python Type | Marker | JSON Example |
|---|---|---|
| tuple | @t | `{"@t": [1, 2, 3]}` |
| bytes | @b | `{"@b": "AQID"}` (base64) |
| set | @set | `{"@set": [1, 2, 3]}` |
| frozenset | @fset | `{"@fset": [1, 2, 3]}` |
| datetime | @dt | `{"@dt": "2025-06-15T12:00:00"}` |
| date | @date | `{"@date": "2025-06-15"}` |
| time | @time | `{"@time": "12:30:45"}` |
| timedelta | @td | `{"@td": [7, 3600, 0]}` |
| Decimal | @dec | `{"@dec": "3.14"}` |
| UUID | @uuid | `{"@uuid": "12345678-..."}` |
| Persistent ref | @ref | `{"@ref": "0000000000000003"}` |
| BTree map data | @kv | `{"@kv": [["a", 1], ["b", 2]]}` |
| BTree set data | @ks | `{"@ks": [1, 2, 3]}` |
| Unknown type | @pkl | `{"@pkl": "base64..."}` (escape hatch) |

ZODB records use @cls (class) + @s (state) markers:
```json
{"@cls": ["myapp.models", "Document"], "@s": {"title": "Hello", "count": 42}}
```

## Performance

Benchmarked against CPython's pickle module (C extension). The codec does more work
(2 conversions + type transformation) yet beats pickle on most operations:

- Encode: 1.7-9.2x faster (synthetic), 3-5x faster (real FileStorage)
- Decode: 1.0-2.3x faster (synthetic), near parity on real-world data
- PG JSON path: 1.3-3.3x faster end-to-end with GIL-free throughput
- Full codec overhead: ~28 us per object (both directions)

## Documentation Sections

- [Tutorials](tutorials/index.md): Getting started, working with ZODB records
- [How-To Guides](how-to/index.md): Install, integrate with pgjsonb, benchmarks, build from source
- [Reference](reference/index.md): Python API, JSON format, BTree format, project structure, changelog
- [Explanation](explanation/index.md): Why JSON, architecture, performance, optimization journal, security
