Powerful universal JSON encoder/decoder for Python objects with support for pydantic v2.
Project description
🚀 Kajson - Universal JSON Encoder/Decoder for Python
Kajson is a powerful drop-in replacement for Python's standard json module that automatically handles complex object serialization, including Pydantic v2 models, datetime objects, and custom types.
This library is used by Pipelex, the open-source language for repeatable AI workflows, check it out.
✨ Why Kajson?
Say goodbye to type X is not JSON serializable!
The Problem with Standard JSON
import json
from datetime import datetime
from pydantic import BaseModel
class User(BaseModel):
name: str
created_at: datetime
user = User(name="Alice", created_at=datetime.now())
# ❌ Standard json fails
json.dumps(user) # TypeError: Object of type User is not JSON serializable
The Kajson Solution
Full example: ex_08_readme_basic_usage.py
import kajson
# ✅ Just works!
json_str = kajson.dumps(user)
restored_user = kajson.loads(json_str)
assert user == restored_user # Perfect reconstruction!
🎯 Key Features
- 🔄 Drop-in replacement - Same API as standard
jsonmodule - 🐍 Pydantic v2 support - Seamless serialization of Pydantic models
- 📅 DateTime handling - Built-in support for date, time, datetime, timedelta
- 🏗️ Type preservation - Automatically preserves and reconstructs original types
- 🏛️ Class registry - Handle dynamic classes from distributed systems and runtime generation
- 🔌 Extensible - Easy registration of custom encoders/decoders
- 🎁 Batteries included - Common types work out of the box
📦 Installation
# Using pip
pip install kajson
# Using poetry
poetry add kajson
# Using uv (recommended)
uv pip install kajson
🚀 Quick Start
Basic Usage
Full example: ex_01_basic_pydantic_serialization.py
from datetime import datetime
from pydantic import BaseModel
from kajson import kajson, kajson_manager
class User(BaseModel):
name: str
email: str
created_at: datetime
def main():
# Create and serialize
user = User(
name="Alice",
email="alice@example.com",
created_at=datetime.now()
)
# Serialize to JSON
json_str = kajson.dumps(user, indent=2)
# Deserialize back
restored_user = kajson.loads(json_str)
assert user == restored_user # ✅ Perfect reconstruction!
if __name__ == "__main__":
kajson_manager.KajsonManager()
main()
Working with Complex Nested Models
Full example: ex_09_readme_complex_nested.py
from datetime import datetime
from typing import Any, Dict, List
from pydantic import BaseModel
class Comment(BaseModel):
author: str
content: str
created_at: datetime
class BlogPost(BaseModel):
title: str
content: str
published_at: datetime
comments: List[Comment]
metadata: Dict[str, Any]
# Create complex nested structure
post = BlogPost(
title="Introducing Kajson",
content="A powerful JSON library...",
published_at=datetime.now(),
comments=[
Comment(author="Alice", content="Great post!", created_at=datetime.now()),
Comment(author="Bob", content="Very helpful", created_at=datetime.now())
],
metadata={"views": 1000, "likes": 50}
)
# Serialize and deserialize - it just works!
json_str = kajson.dumps(post)
restored_post = kajson.loads(json_str)
# All nested objects are perfectly preserved
assert isinstance(restored_post.comments[0], Comment)
assert restored_post.comments[0].created_at.year == datetime.now().year
🔧 Advanced Features
Custom Type Registration
Full example: ex_10_readme_custom_registration.py
Register encoders/decoders for any type:
from decimal import Decimal
from pathlib import Path
from typing import Any, Dict
import kajson
# Register Decimal support
def encode_decimal(value: Decimal) -> Dict[str, str]:
return {"decimal": str(value)}
def decode_decimal(data: Dict[str, str]) -> Decimal:
return Decimal(data["decimal"])
kajson.UniversalJSONEncoder.register(Decimal, encode_decimal)
kajson.UniversalJSONDecoder.register(Decimal, decode_decimal)
# Now Decimal works seamlessly
data = {"price": Decimal("19.99"), "tax": Decimal("1.50")}
json_str = kajson.dumps(data)
restored = kajson.loads(json_str)
assert restored["price"] == Decimal("19.99") # ✅
# Register Path support
kajson.UniversalJSONEncoder.register(
Path,
lambda p: {"path": str(p)}
)
kajson.UniversalJSONDecoder.register(
Path,
lambda d: Path(d["path"])
)
# Path objects now work too!
config = {"home": Path.home(), "config": Path("/etc/myapp/config.json")}
restored_config = kajson.loads(kajson.dumps(config))
Custom Classes with Hooks
Full example: ex_11_readme_custom_hooks.py
Add JSON support to your own classes:
from typing import Any, Dict
from typing_extensions import override
class Vector:
def __init__(self, x: float, y: float):
self.x = x
self.y = y
def __json_encode__(self):
"""Called by Kajson during serialization"""
return {"x": self.x, "y": self.y}
@classmethod
def __json_decode__(cls, data: Dict[str, Any]):
"""Called by Kajson during deserialization"""
return cls(data["x"], data["y"])
@override
def __eq__(self, other: object) -> bool:
if not isinstance(other, Vector):
return False
return self.x == other.x and self.y == other.y
# Works automatically!
vector = Vector(3.14, 2.71)
restored = kajson.loads(kajson.dumps(vector))
assert vector == restored
Working with Mixed Types
Full example: ex_12_readme_mixed_types.py
from datetime import datetime, timedelta
from typing import Any, Dict
from pydantic import BaseModel
class Task(BaseModel):
name: str
created_at: datetime
duration: timedelta
metadata: Dict[str, Any]
# Create mixed-type list
tasks = [
Task(
name="Data processing",
created_at=datetime.now(),
duration=timedelta(hours=2, minutes=30),
metadata={"priority": "high", "cpu_cores": 8}
),
{"raw_data": "Some plain dict"},
datetime.now(),
["plain", "list", "items"],
]
# Kajson handles everything!
json_str = kajson.dumps(tasks)
restored_tasks = kajson.loads(json_str)
# Type checking shows proper reconstruction
assert isinstance(restored_tasks[0], Task)
assert isinstance(restored_tasks[0].duration, timedelta)
assert isinstance(restored_tasks[2], datetime)
🛡️ Error Handling
Full example: ex_13_readme_error_handling.py
Kajson provides clear error messages for validation issues:
from pydantic import BaseModel, Field
class Product(BaseModel):
name: str
price: float = Field(gt=0) # Price must be positive
# Invalid data
json_str = '{"name": "Widget", "price": -10, "__class__": "Product", "__module__": "__main__"}'
try:
product = kajson.loads(json_str)
except kajson.KajsonDecoderError as e:
print(f"Validation failed: {e}")
# Output: Validation failed: Could not instantiate pydantic BaseModel...
Dynamic Class Registry
Full example: ex_14_dynamic_class_registry.py
Kajson includes a powerful class registry for handling dynamically created classes that aren't available in standard module paths:
from kajson import kajson, kajson_manager
from kajson.kajson_manager import KajsonManager
# Simulate dynamic class creation (e.g., from network, workflow definition)
remote_class_definition = '''
from pydantic import BaseModel, Field
class RemoteTask(BaseModel):
task_id: str
name: str
priority: int = Field(default=1, ge=1, le=10)
'''
# Execute and create the class dynamically
remote_namespace = {}
exec(remote_class_definition, remote_namespace)
RemoteTask = remote_namespace["RemoteTask"]
# Set module to simulate it's not available locally
RemoteTask.__module__ = "remote.distributed.system"
# Create and serialize
task = RemoteTask(task_id="TASK_001", name="Process Data", priority=5)
json_str = kajson.dumps(task)
# Clear local definition (simulate distributed scenario)
del remote_namespace["RemoteTask"]
# Register in class registry for deserialization
registry = KajsonManager.get_class_registry()
registry.register_class(RemoteTask)
# Now deserialization works via class registry!
restored_task = kajson.loads(json_str)
assert restored_task.task_id == "TASK_001"
The Class Registry is essential for:
- 🌐 Distributed systems - Classes defined across different services
- ⚙️ Workflow orchestrators - Dynamic task definitions at runtime
- 🔌 Plugin systems - Runtime-loaded classes from plugins
- 🚀 Microservices - Exchanging complex object definitions
- 🏭 Dynamic generation - Any runtime class creation scenarios
🤝 Compatibility
- Python: 3.9+
- Pydantic: v2.x
- Dependencies: Minimal, only standard library + pydantic
🔄 Migration from Standard JSON
Migrating is as simple as changing your import:
# Before
import json
data = json.dumps(my_object) # Often fails with complex objects
# After
import kajson as json # Drop-in replacement!
data = json.dumps(my_object) # Works with complex objects
Or use Kajson's convenience functions directly:
import kajson
data = kajson.dumps(my_object)
🏗️ How It Works
Kajson extends the standard JSON encoder/decoder by:
- Type Preservation: Adds
__class__and__module__metadata to JSON objects - Smart Decoding: Automatically reconstructs original Python objects
- Registry System: Allows registration of custom encoders/decoders
- Pydantic Integration: Special handling for Pydantic models and validation
📚 Use Cases
- REST APIs: Serialize Pydantic models for API responses
- Data Persistence: Save complex objects to JSON files
- Message Queues: Send rich objects through Redis/RabbitMQ
- Configuration: Store config with proper types
- Data Science: Serialize numpy arrays, pandas DataFrames (with custom encoders)
🔗 Examples
All code examples from this README are available as executable files in the examples/ directory:
ex_01_basic_pydantic_serialization.py- Basic Pydantic model serializationex_02_nested_models_mixed_types.py- Complex nested models with datetime and timedeltaex_03_custom_classes_json_hooks.py- Point class using__json_encode__/__json_decode__hooksex_04_registering_custom_encoders.py- Custom type registrationex_05_mixed_types_lists.py- Lists containing different types (Task, datetime, dict, list, time)ex_06_error_handling_validation.py- Error handling and validationex_07_drop_in_replacement.py- Drop-in replacement for standard JSONex_08_readme_basic_usage.py- Why Kajson? (README example)ex_09_readme_complex_nested.py- Complex nested models (README example)ex_10_readme_custom_registration.py- Custom type registration (README example)ex_11_readme_custom_hooks.py- Custom hooks (README example)ex_12_readme_mixed_types.py- Mixed types (README example)ex_13_readme_error_handling.py- Error handling (README example)ex_14_dynamic_class_registry.py- Dynamic class registry for distributed systems and runtime class generation
Run any example with:
cd examples
python ex_01_basic_pydantic_serialization.py
Credits
This project is heavily based on the excellent work from unijson by Bastien Pietropaoli and distributed under the same license, Apache 2.0.
License
© 2025 Evotis S.A.S. - Licensed under Apache 2.0
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file kajson-0.2.1.tar.gz.
File metadata
- Download URL: kajson-0.2.1.tar.gz
- Upload date:
- Size: 18.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b743a441144d5856578f5fe54c0b0ced5609d7ff72351082d5faed80506186a4
|
|
| MD5 |
ad83bd7d9e0da681e658904a1ff8466b
|
|
| BLAKE2b-256 |
4ee022fc94e29241790ec5537c131ce0868866f0b4b8f7cf62b6490fc3e80c4a
|
Provenance
The following attestation bundles were made for kajson-0.2.1.tar.gz:
Publisher:
publish-pypi.yml on Pipelex/kajson
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
kajson-0.2.1.tar.gz -
Subject digest:
b743a441144d5856578f5fe54c0b0ced5609d7ff72351082d5faed80506186a4 - Sigstore transparency entry: 248470835
- Sigstore integration time:
-
Permalink:
Pipelex/kajson@862a625e8f1684b8e764ba7f20b41d44ba09620c -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Pipelex
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@862a625e8f1684b8e764ba7f20b41d44ba09620c -
Trigger Event:
push
-
Statement type:
File details
Details for the file kajson-0.2.1-py3-none-any.whl.
File metadata
- Download URL: kajson-0.2.1-py3-none-any.whl
- Upload date:
- Size: 24.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0f23cf3f26ef13cf63356b1fa589ae555f00655768349e6f81dc24106bfee393
|
|
| MD5 |
a0fd315a4e43e9cc5ec1a2b2a28aad28
|
|
| BLAKE2b-256 |
01510315043d7c211c38be717dd0221605ec82bc23bc4ebbf62542eee1b047e5
|
Provenance
The following attestation bundles were made for kajson-0.2.1-py3-none-any.whl:
Publisher:
publish-pypi.yml on Pipelex/kajson
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
kajson-0.2.1-py3-none-any.whl -
Subject digest:
0f23cf3f26ef13cf63356b1fa589ae555f00655768349e6f81dc24106bfee393 - Sigstore transparency entry: 248470837
- Sigstore integration time:
-
Permalink:
Pipelex/kajson@862a625e8f1684b8e764ba7f20b41d44ba09620c -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Pipelex
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@862a625e8f1684b8e764ba7f20b41d44ba09620c -
Trigger Event:
push
-
Statement type: