A high-performance Python library for seamless serialization and deserialization of complex Python objects to/from JSON format.
JsonPort provides intelligent type handling, caching optimizations, and comprehensive support for dataclasses, enums, datetime objects, collections, and extended standard library types with blazing fast performance! β‘
| Feature | Description |
|---|---|
| π High Performance | Introspected type caching and optimized conversion algorithms |
| π― Type Safety | PEP 561 compliant (py.typed) with full static analysis support (mypy --strict) |
| π¦ Dataclasses & Modern Typing | Full support for dataclass, NamedTuple, TypedDict, Literal, and intelligent Union matching |
| π§© Extended Types | Native serialization/deserialization for UUID, Decimal, Path, bytes, IP addresses/networks, timedelta, and regex Pattern |
| π Direct String & Stream I/O | dumps() and loads() for strings; dump_stream() and load_stream() for files/network/StringIO |
| π·οΈ Field Metadata | Fine-grained dataclass control: alias, exclude, and per-field custom serializer / deserializer |
| π Custom Registry | Extensible @serializer and @deserializer decorators, functional APIs, and isolated Registry instances |
| π‘οΈ Strict Mode & Error Paths | Enforce exact schema match with strict=True, pinpoint errors with attribute paths (e.g. user.address.zip) |
| π File & Compression | Direct file I/O with automatic .gz gzip compression/decompression |
| π§ Zero Dependencies | Pure Python implementation with zero third-party dependencies |
pip install jsonportJsonPort 2.0 maintains 100% backward compatibility with all 1.x functions:
from dataclasses import dataclass
from datetime import datetime
from enum import Enum
from jsonport import dump, load, dump_file, load_file
class UserRole(Enum):
ADMIN = "admin"
USER = "user"
@dataclass
class User:
name: str
age: int
role: UserRole
created_at: datetime
tags: list[str]
user = User(
name="John Doe",
age=30,
role=UserRole.ADMIN,
created_at=datetime.now(),
tags=["developer", "python"],
)
# Serialize to dictionary/primitives
data = dump(user)
print(data["role"]) # "admin"
# Deserialize back to dataclass
restored_user = load(data, User)
assert restored_user.name == "John Doe"
# Save & load directly from JSON or gzipped files
dump_file(user, "user.json.gz")
loaded_user = load_file("user.json.gz", User)import io
from jsonport import dumps, loads, dump_stream, load_stream
# Direct JSON string serialization and deserialization
json_str = dumps(user, indent=2)
user_from_str = loads(json_str, User)
# Stream operations with StringIO, files, or network streams
stream = io.StringIO()
dump_stream(user, stream)
stream.seek(0)
user_from_stream = load_stream(stream, User)import uuid
import decimal
import pathlib
import ipaddress
from dataclasses import dataclass, field
from jsonport import dumps, loads
@dataclass
class ServerNode:
id: uuid.UUID
rate: decimal.Decimal
config_path: pathlib.Path
ip: ipaddress.IPv4Address
# Field aliases and exclusions
secret_token: str = field(metadata={"exclude": True})
display_name: str = field(metadata={"alias": "server_name"})
node = ServerNode(
id=uuid.uuid4(),
rate=decimal.Decimal("99.99"),
config_path=pathlib.Path("/etc/server.conf"),
ip=ipaddress.IPv4Address("192.168.1.1"),
secret_token="super-secret",
display_name="Node-Alpha",
)
json_data = dumps(node)
# {"id": "...", "rate": "99.99", "config_path": "/etc/server.conf", "ip": "192.168.1.1", "server_name": "Node-Alpha"}
restored_node = loads(json_data, ServerNode)from jsonport import serializer, deserializer, dumps, loads
class Money:
def __init__(self, amount: float, currency: str):
self.amount = amount
self.currency = currency
@serializer(Money)
def serialize_money(m: Money) -> str:
return f"{m.currency} {m.amount:.2f}"
@deserializer(Money)
def deserialize_money(s: str) -> Money:
curr, amt = s.split()
return Money(float(amt), curr)
json_str = dumps(Money(49.95, "USD"))
# '"USD 49.95"'
money = loads(json_str, Money)from jsonport import loads, DeserializationError
raw_json = '{"name": "Alice", "unexpected_field": 123}'
try:
loads(raw_json, User, strict=True)
except DeserializationError as err:
print(err)
# Output: Extra keys not permitted in strict mode for User: ['unexpected_field']
print(err.target_type) # <class 'User'>JsonPort 2.0 supports modern Python versions:
| Version | Status |
|---|---|
| Python 3.9 | β Full Support |
| Python 3.10 | β Full Support |
| Python 3.11 | β Full Support |
| Python 3.12 | β Full Support |
| Python 3.13 | β Full Support |
from dataclasses import dataclass
from typing import List, Dict, Optional, Tuple
from datetime import date
@dataclass
class Address:
street: str
city: str
country: str
postal_code: str
@dataclass
class Contact:
email: str
phone: Optional[str] = None
@dataclass
class Company:
name: str
founded: date
employees: int
address: Address
contacts: List[Contact]
departments: Dict[str, List[str]]
# Create complex object
company = Company(
name="TechCorp",
founded=date(2020, 1, 1),
employees=150,
address=Address(
street="123 Tech Street",
city="San Francisco",
country="USA",
postal_code="94105"
),
contacts=[
Contact("info@techcorp.com"),
Contact("support@techcorp.com", "+1-555-0123")
],
departments={
"Engineering": ["Backend", "Frontend", "DevOps"],
"Sales": ["Enterprise", "SMB"],
"Marketing": ["Digital", "Content"]
}
)
# Serialize complex structure
data = dump(company)
# Deserialize with full type preservation
restored_company = load(data, Company)from dataclasses import dataclass
from typing import Set, Tuple
@dataclass
class Product:
id: int
name: str
price: float
categories: Set[str]
dimensions: Tuple[float, float, float]
product = Product(
id=1,
name="Laptop",
price=999.99,
categories={"electronics", "computers", "portable"},
dimensions=(35.5, 24.0, 2.1)
)
# Serialize with collection type preservation
data = dump(product)
# Sets are converted to lists, tuples preserved
print(data["categories"]) # ["electronics", "computers", "portable"]
print(data["dimensions"]) # [35.5, 24.0, 2.1]
# Deserialize with proper type restoration
restored_product = load(data, Product)
print(type(restored_product.categories)) # <class 'set'>
print(type(restored_product.dimensions)) # <class 'tuple'>import json
from jsonport import JsonPortEncoder
# Use the custom encoder with standard json module
data = dump(company)
json_string = json.dumps(data, cls=JsonPortEncoder, indent=2)
print(json_string)from jsonport import JsonPortError
try:
# Try to serialize non-serializable object
non_serializable = lambda x: x
dump(non_serializable)
except JsonPortError as e:
print(f"Serialization error: {e}")
try:
# Try to load file that doesn't exist
load_file("nonexistent.json", User)
except FileNotFoundError:
print("File not found")
except JsonPortError as e:
print(f"Deserialization error: {e}")JsonPort automatically caches:
- Type hints for dataclasses (max 1024 entries)
- Optional type resolution (max 512 entries)
This provides significant performance improvements when working with the same dataclass types repeatedly.
import time
from dataclasses import dataclass
from jsonport import dump, load
@dataclass
class BenchmarkData:
id: int
name: str
values: list[float]
metadata: dict[str, str]
# Create test data
test_data = BenchmarkData(
id=1,
name="test",
values=[1.1, 2.2, 3.3] * 1000,
metadata={"key1": "value1", "key2": "value2"}
)
# Benchmark serialization
start_time = time.time()
for _ in range(1000):
data = dump(test_data)
serialization_time = time.time() - start_time
# Benchmark deserialization
start_time = time.time()
for _ in range(1000):
restored = load(data, BenchmarkData)
deserialization_time = time.time() - start_time
print(f"Serialization: {serialization_time:.4f}s")
print(f"Deserialization: {deserialization_time:.4f}s")JsonPort uses pytest for all automated tests. To run the test suite:
pip install -e ".[test]"# All tests
pytest -v
# With coverage
pytest --cov
# Only fast unit tests
pytest -m 'not slow and not integration' -v
# Only performance tests
pytest -m slow -v
# Only integration tests
pytest -m integration -v# Run performance benchmarks
pytest --benchmark-only -vExample output:
--------------------------------------------------------------------------------------------- benchmark: 2 tests -----------------------------------------------------------------------------
Name (time in us) Min Max Mean StdDev Median IQR Outliers OPS (Kops/s) Rounds Iterations
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
test_deserialization_benchmark 110.3460 (1.0) 263.2940 (1.0) 120.8443 (1.0) 12.3452 (1.0) 118.4470 (1.0) 6.0770 (1.0) 386;464 8.2751 (1.0) 6829 1
test_serialization_benchmark 251.4210 (2.28) 522.7470 (1.99) 270.2584 (2.24) 16.9108 (1.37) 266.3670 (2.25) 12.2920 (2.02) 218;161 3.7002 (0.45) 2499 1
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Function | Description | Parameters | Returns |
|---|---|---|---|
dump(obj) |
Serialize object to JSON-serializable format | obj: Object to serialize |
JSON-serializable data |
load(data, target_class) |
Deserialize JSON data to Python object | data: Data to deserializetarget_class: Target class |
Instance of target class |
dump_file(obj, path, overwrite=True) |
Serialize object and save to file | obj: Object to serializepath: File pathoverwrite: Overwrite existing file |
None |
load_file(path, target_class) |
Load JSON file and deserialize | path: File pathtarget_class: Target class |
Instance of target class |
| Category | Types |
|---|---|
| Primitives | str, int, float, bool |
| Datetime | datetime.datetime, datetime.date, datetime.time |
| Collections | list, tuple, set, dict |
| Custom Types | dataclass, Enum |
| Optional Types | Optional[T], Union[T, None] |
Always define proper type hints for optimal performance and type safety:
@dataclass
class User:
name: str
age: int
email: Optional[str] = None
tags: List[str] = NoneUse Optional types for fields that might be None:
@dataclass
class Product:
id: int
name: str
description: Optional[str] = None
price: Optional[float] = NoneChoose the right collection type for your data:
@dataclass
class Configuration:
settings: Dict[str, Any]
allowed_users: Set[str]
coordinates: Tuple[float, float]
items: List[str]Always handle potential errors in production code:
try:
data = load_file("config.json", Config)
except (FileNotFoundError, JsonPortError) as e:
logger.error(f"Failed to load config: {e}")
data = Config() # Use default configWe welcome contributions! Here's how you can help:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
# Clone the repository
git clone https://github.com/Luan1Schons/JsonPort.git
cd JsonPort
# Install in development mode
pip install -e ".[dev,test]"
# Run tests
pytest -v
# Format code
black jsonport/ tests/
# Check code quality
flake8 jsonport/ tests/ --max-line-length=88 --extend-ignore=E203,W503,E501,F401,F811,F841,E731This project is licensed under the MIT License - see the LICENSE file for details.
If you encounter any issues or have questions, please:
- π Check the documentation
- π Search existing issues
- π Create a new issue
JsonPort - Making JSON serialization simple, fast, and type-safe! π