Jsonic is a lightweight, Pythonic library for effortless JSON serialization and deserialization of Python objects. Built for modern Python with type hints, dataclasses, and developer experience in mind.
from jsonic import serialize, deserialize
from dataclasses import dataclass
from datetime import datetime
@dataclass
class User:
name: str
email: str
created_at: datetime
user = User("Alice", "alice@example.com", datetime.now())
json_data = serialize(user) # Dict or JSON string
user_copy = deserialize(json_data, expected_type=User) # Type-safe!- ๐ฏ Zero Configuration - Works with dataclasses, type hints, and regular classes
- ๐ Type Safe - Full type hint support with validation
- ๐ Modern Python - Built for Python 3.8+ with dataclasses and type hints
- ๐ฆ Rich Types - Supports tuples, sets, enums, datetime,
__slots__, and more - ๐จ Flexible - Custom serializers, transient fields, private attribute control
- ๐ Partial Serialization - Include/exclude fields with nested dot notation
- ๐ค Pydantic Integration - Seamless support for Pydantic models and field aliases
- ๐ Great Errors - Detailed error messages with exact path to the problem
- โก Fast - Minimal overhead, optimized for performance
- ๐งช Well Tested - 448 tests with >95% coverage
pip install py-jsonicRequirements: Python 3.8+
from jsonic import serialize, deserialize
from dataclasses import dataclass
@dataclass
class Product:
name: str
price: float
in_stock: bool
# Serialize to dict
product = Product("Laptop", 999.99, True)
data = serialize(product)
# {'name': 'Laptop', 'price': 999.99, 'in_stock': True, '_serialized_type': 'Product'}
# Deserialize back to object
product_copy = deserialize(data, expected_type=Product)
assert product.name == product_copy.namefrom dataclasses import dataclass
from typing import List
@dataclass
class Address:
street: str
city: str
@dataclass
class User:
name: str
addresses: List[Address]
user = User("Bob", [Address("123 Main St", "NYC")])
data = serialize(user)
user_copy = deserialize(data, expected_type=User)json_string = serialize(user, string_output=True)
user_copy = deserialize(json_string, string_input=True, expected_type=User)Jsonic automatically handles:
- Primitives:
int,float,str,bool,None - Collections:
list,dict,tuple,set - Standard Library:
datetime,Enum,UUID - Python Classes: dataclasses, classes with type hints,
__slots__ - Custom Types: Via
@jsonic_serializerand@jsonic_deserializer
from dataclasses import dataclass
from jsonic import serialize, deserialize
@dataclass
class Person:
name: str
age: int
person = Person("Alice", 30)
data = serialize(person)from jsonic import Serializable
from datetime import datetime
class User(Serializable):
def __init__(self, username: str, created_at: datetime):
super().__init__()
self.username = username
self.created_at = created_at
user = User("alice", datetime.now())
data = serialize(user)from jsonic import register_jsonic_type
class ThirdPartyClass:
def __init__(self, value: str):
self.internal_value = value
# Register with parameter mapping
register_jsonic_type(
ThirdPartyClass,
init_parameters_mapping={'value': 'internal_value'}
)@dataclass
class APIResponse:
status: str
data: dict
timestamp: datetime
response = APIResponse("success", {"user_id": 123}, datetime.now())
return serialize(response, string_output=True)@dataclass
class BlogPost:
title: str
content: str
author: User
tags: List[str]
published_at: datetime
# Save to database
post_json = serialize(post, string_output=True)
db.save(post_json)
# Load from database
post_data = db.load()
post = deserialize(post_data, string_input=True, expected_type=BlogPost)@dataclass
class AppConfig:
database_url: str
api_keys: dict
features: List[str]
# Load config
with open('config.json') as f:
config = deserialize(json.load(f), expected_type=AppConfig)@dataclass
class OrderEvent:
order_id: str
items: List[Product]
total: float
created_at: datetime
# Send event
event = OrderEvent("ORD-123", products, 299.99, datetime.now())
message_queue.publish(serialize(event, string_output=True))
# Receive event
data = message_queue.consume()
event = deserialize(data, string_input=True, expected_type=OrderEvent)Control which fields to serialize with include and exclude parameters:
@dataclass
class User:
username: str
email: str
password_hash: str
api_token: str
user = User("alice", "alice@example.com", "hash123", "token456")
# Exclude sensitive fields
safe_data = serialize(user, exclude={'password_hash', 'api_token'})
# Result: {'username': 'alice', 'email': 'alice@example.com', ...}
# Include only specific fields
public_data = serialize(user, include={'username', 'email'})
# Result: {'username': 'alice', 'email': 'alice@example.com', ...}Nested field filtering with dot notation:
@dataclass
class Credentials:
username: str
password: str
@dataclass
class Database:
host: str
credentials: Credentials
@dataclass
class Config:
app_name: str
database: Database
config = Config("MyApp", Database("localhost", Credentials("admin", "secret")))
# Exclude nested password field
safe_config = serialize(config, exclude={'database.credentials.password'})
# Password is excluded, but username remains
# Include only specific nested fields
public_config = serialize(config, include={'app_name', 'database.host'})
# Only app_name and database.host are includedFilter fields in lists:
@dataclass
class Item:
name: str
price: float
internal_cost: float
@dataclass
class Order:
order_id: str
items: List[Item]
order = Order("ORD-123", [Item("Widget", 10.0, 5.0)])
# Exclude internal_cost from all items
public_order = serialize(order, exclude={'items.internal_cost'})Exclude fields from serialization at the class level:
class User(Serializable):
transient_attributes = ['password_hash', '_cache']
def __init__(self, username: str, password_hash: str):
super().__init__()
self.username = username
self.password_hash = password_hash # Won't be serialized
self._cache = {} # Won't be serializedControl private attribute serialization:
# Exclude private attributes (default)
data = serialize(obj, serialize_private_attributes=False)
# Include private attributes
data = serialize(obj, serialize_private_attributes=True)from jsonic import jsonic_serializer, jsonic_deserializer
from decimal import Decimal
@jsonic_serializer(Decimal)
def serialize_decimal(obj: Decimal) -> dict:
return {'value': str(obj), '_serialized_type': 'Decimal'}
@jsonic_deserializer('Decimal')
def deserialize_decimal(data: dict) -> Decimal:
return Decimal(data['value'])# Validates the deserialized type matches expected type
user = deserialize(data, expected_type=User)
# Raises error if type doesn't match
try:
product = deserialize(user_data, expected_type=Product)
except TypeError as e:
print(f"Type mismatch: {e}")from enum import Enum
class Status(Enum):
PENDING = "pending"
APPROVED = "approved"
REJECTED = "rejected"
@dataclass
class Request:
status: Status
request = Request(Status.APPROVED)
data = serialize(request) # Handles enums automatically@dataclass
class Data:
coordinates: tuple # (x, y, z)
unique_ids: set
data = Data((1.0, 2.0, 3.0), {1, 2, 3})
serialized = serialize(data) # Preserves tuple and set types
restored = deserialize(serialized, expected_type=Data)
assert isinstance(restored.coordinates, tuple)
assert isinstance(restored.unique_ids, set)Jsonic seamlessly integrates with Pydantic models:
from pydantic import BaseModel, Field
class User(BaseModel):
name: str
email: str
age: int = Field(ge=0, le=150)
nickname: str = Field(alias="display_name")
# Serialize Pydantic models
user = User(name="Alice", email="alice@example.com", age=30, display_name="Ally")
data = serialize(user) # Respects field aliases
# Deserialize to Pydantic models
user_copy = deserialize(data, expected_type=User) # Full validationPydantic features supported:
- Auto-detection of Pydantic models
- Field aliases (
alias,validation_alias,serialization_alias) - Nested Pydantic models
- Pydantic validators run on deserialization
Jsonic provides detailed error messages with exact paths:
@dataclass
class Address:
street: str
city: str
@dataclass
class User:
name: str
address: Address
# Error shows exact location
try:
data = {'name': 'Alice', 'address': {'street': 123}} # Wrong type
user = deserialize(data, expected_type=User)
except Exception as e:
print(e)
# DeserializationError: Type mismatch at path: obj.address.streettry:
deserialize({'_serialized_type': 'Usr'}) # Typo
except TypeError as e:
print(e)
# Could not find type: Usr
# Did you mean one of these?
# - User
# - UserProfile| Feature | Jsonic | Pydantic | marshmallow | dataclasses-json |
|---|---|---|---|---|
| Zero config for dataclasses | โ | โ | โ | โ |
| Type hints support | โ | โ | โ | |
| Pydantic integration | โ | N/A | โ | โ |
| Partial serialization | โ | โ | ||
| Nested field filtering | โ | โ | โ | โ |
| Validation | โ | โ | โ | |
| Tuples/Sets support | โ | โ | โ | |
__slots__ support |
โ | โ | โ | โ |
| Custom serializers | โ | โ | โ | |
| Error messages with paths | โ | โ | โ | |
| Learning curve | Low | Medium | High | Low |
| Performance | Fast | Fast | Medium | Fast |
When to use Jsonic:
- You want simple, Pythonic serialization without schemas
- You're working with existing classes you can't modify
- You need
__slots__, tuples, or sets support - You want great error messages out of the box
- You need fine-grained control over serialization (include/exclude nested fields)
- You want to work with both dataclasses and Pydantic models
When to use alternatives:
- Pydantic: You need extensive validation and FastAPI integration (but Jsonic can serialize Pydantic models!)
- marshmallow: You need complex validation rules and transformations
- dataclasses-json: You only need basic dataclass serialization
See EXAMPLES.md for comprehensive examples including:
- Real-world API integration
- Database persistence patterns
- Microservices communication
- Configuration management
- Migration from other libraries
Contributions are welcome! See CONTRIBUTING.md for:
- Development setup
- Running tests
- Code style guidelines
- Pull request process
- Examples - Real-world usage examples
- API Reference - Detailed API documentation
- Roadmap - Planned features and timeline
- Contributing - How to contribute
Jsonic is designed for data classes and may not work with:
- Classes with temporary constructor parameters - Parameters not stored as attributes
- Classes with positional-only parameters - Can't reconstruct with keyword args
- Classes with complex
*args/**kwargs- May not deserialize correctly - Classes with side effects in
__init__- May behave differently on deserialization
For these cases, consider using custom serializers or restructuring your classes.
MIT License - see LICENSE file for details.
Inspired by the Python community's need for simple, Pythonic serialization that "just works" with modern Python features.
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Email: orrbenyamini@gmail.com
Made with โค๏ธ for the Python community