Serialization
Crous provides four core functions for serialization: dumps/loads for in-memory operations and dump/load for file/stream I/O.
In-Memory Serialization
dumps(obj, default=None)
Serialize a Python object to FLUX binary bytes.
import crous
# Serialize to bytes
data = {"key": "value", "numbers": [1, 2, 3]}
binary = crous.dumps(data)
print(type(binary)) # <class 'bytes'>
print(len(binary)) # compact binary representationloads(data, object_hook=None)
Deserialize FLUX binary bytes back to a Python object. Automatically detects FLUX vs legacy CROUS format from the magic bytes.
import crous
binary = crous.dumps({"name": "Alice", "age": 30})
# Deserialize from bytes
result = crous.loads(binary)
print(result) # {'name': 'Alice', 'age': 30}File I/O
dump(obj, file, default=None)
Serialize a Python object and write it to a file. Accepts both file paths (strings) and file-like objects.
import crous
data = {"users": [{"name": "Alice"}, {"name": "Bob"}]}
# Using a file path
crous.dump(data, "users.crous")
# Using a file object
with open("users.crous", "wb") as f:
crous.dump(data, f)load(file, object_hook=None)
Read and deserialize from a file. Accepts both file paths and file-like objects.
import crous
# From file path
data = crous.load("users.crous")
# From file object
with open("users.crous", "rb") as f:
data = crous.load(f)
print(data) # {'users': [{'name': 'Alice'}, {'name': 'Bob'}]}The default Parameter
The default parameter lets you handle types that Crous doesn't natively support. It receives the unsupported object and should return a serializable value.
import crous
from datetime import datetime, date
from decimal import Decimal
def default_handler(obj):
if isinstance(obj, datetime):
return obj.isoformat()
if isinstance(obj, date):
return obj.isoformat()
if isinstance(obj, Decimal):
return float(obj)
raise TypeError(f"Cannot serialize {type(obj)}")
data = {
"timestamp": datetime.now(),
"price": Decimal("19.99"),
}
binary = crous.dumps(data, default=default_handler)
result = crous.loads(binary)
print(result) # {'timestamp': '2024-...', 'price': 19.99}One-way with default
default parameter converts objects to basic types, so the original type information is lost on decode. For round-trip type preservation, use custom serializers instead.The object_hook Parameter
The object_hook parameter is called for every decoded dictionary, letting you transform the result.
import crous
def hook(d):
if "type" in d and d["type"] == "user":
return User(d["name"], d["age"])
return d
class User:
def __init__(self, name, age):
self.name = name
self.age = age
data = {"type": "user", "name": "Alice", "age": 30}
binary = crous.dumps(data)
user = crous.loads(binary, object_hook=hook)
print(f"{user.name}, {user.age}") # Alice, 30Encoder & Decoder Classes
For repeated operations with the same options, use the class-based API:
import crous
# Create reusable encoder with default handler
encoder = crous.CrousEncoder(default=my_default_handler)
binary = encoder.encode(data)
# Create reusable decoder with object hook
decoder = crous.CrousDecoder(object_hook=my_hook)
result = decoder.decode(binary)FLUX Binary Format
As of v2.0.0, Crous uses the FLUX binary format by default. FLUX provides:
- Zigzag varint integers — small integers encoded in 1-2 bytes instead of 8
- Small-int optimization — integers 0–24 use a single byte
- Big-endian floats — IEEE 754 double precision
- Varint lengths — container sizes use 7-bit LEB128 encoding
FLUX Header
# FLUX binary header structure:
# [F][L][U][X] [version: 1 byte] [flags: 1 byte]
# 4 bytes 0x01 0x00
# Total header: 6 bytesAuto-Detection
crous.loads() automatically detects the format from magic bytes. FLUX data starts with b"FLUX", legacy CROUS data starts with b"CROU". Both formats are transparently decoded.