Skip to main content

lightweight utility for JSON serialization

Project description

Jsonic

PyPI version Python 3.8+ License: MIT Tests

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!

โœจ Features

  • ๐ŸŽฏ 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

๐Ÿ“ฆ Installation

pip install py-jsonic

Requirements: Python 3.8+


๐Ÿš€ Quick Start

Basic Usage

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.name

With Nested Objects

from 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 Output

json_string = serialize(user, string_output=True)
user_copy = deserialize(json_string, string_input=True, expected_type=User)

๐Ÿ“š Core Concepts

Supported Types

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_serializer and @jsonic_deserializer

Three Ways to Use Jsonic

1. Dataclasses (Recommended)

from dataclasses import dataclass
from jsonic import serialize, deserialize

@dataclass
class Person:
    name: str
    age: int

person = Person("Alice", 30)
data = serialize(person)

2. Serializable Base Class

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)

3. Register External Classes

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'}
)

๐ŸŽฏ Common Use Cases

API Responses

@dataclass
class APIResponse:
    status: str
    data: dict
    timestamp: datetime

response = APIResponse("success", {"user_id": 123}, datetime.now())
return serialize(response, string_output=True)

Database Models

@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)

Configuration Files

@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)

Microservices Communication

@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)

๐Ÿ”ง Advanced Features

Partial Serialization

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 included

Filter 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'})

Transient Attributes

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 serialized

Private Attributes

Control private attribute serialization:

# Exclude private attributes (default)
data = serialize(obj, serialize_private_attributes=False)

# Include private attributes
data = serialize(obj, serialize_private_attributes=True)

Custom Serializers

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'])

Type Safety

# 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}")

Enums

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

Tuples and Sets

@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)

Pydantic Models

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 validation

Pydantic features supported:

  • Auto-detection of Pydantic models
  • Field aliases (alias, validation_alias, serialization_alias)
  • Nested Pydantic models
  • Pydantic validators run on deserialization

๐Ÿ› Error Handling

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.street

Type Not Found Suggestions

try:
    deserialize({'_serialized_type': 'Usr'})  # Typo
except TypeError as e:
    print(e)
    # Could not find type: Usr
    # Did you mean one of these?
    #   - User
    #   - UserProfile

๐Ÿ“Š Comparison with Alternatives

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

๐ŸŽ“ Examples

See EXAMPLES.md for comprehensive examples including:

  • Real-world API integration
  • Database persistence patterns
  • Microservices communication
  • Configuration management
  • Migration from other libraries

๐Ÿค Contributing

Contributions are welcome! See CONTRIBUTING.md for:

  • Development setup
  • Running tests
  • Code style guidelines
  • Pull request process

๐Ÿ“ Documentation


๐Ÿ› Known Limitations

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.


๐Ÿ“„ License

MIT License - see LICENSE file for details.


๐Ÿ™ Acknowledgments

Inspired by the Python community's need for simple, Pythonic serialization that "just works" with modern Python features.


๐Ÿ“ฎ Support


Made with โค๏ธ for the Python community

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

py_jsonic-1.0.1.tar.gz (64.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

py_jsonic-1.0.1-py3-none-any.whl (76.3 kB view details)

Uploaded Python 3

File details

Details for the file py_jsonic-1.0.1.tar.gz.

File metadata

  • Download URL: py_jsonic-1.0.1.tar.gz
  • Upload date:
  • Size: 64.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.0

File hashes

Hashes for py_jsonic-1.0.1.tar.gz
Algorithm Hash digest
SHA256 714bae343a7e2b6011239a24ad8318ac944aec7d62290677034821ffa3e6ab20
MD5 e4302fb7d7f2a0de5ffad54d5135f8ed
BLAKE2b-256 1c84223df2c4537b877175676460bb21b348d12f2ed9f1f5d990a5172fd0293e

See more details on using hashes here.

File details

Details for the file py_jsonic-1.0.1-py3-none-any.whl.

File metadata

  • Download URL: py_jsonic-1.0.1-py3-none-any.whl
  • Upload date:
  • Size: 76.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.0

File hashes

Hashes for py_jsonic-1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 534ac726f0d82122f8796cbbf0fcd8ca82385aae3f7be297243b4b4e1dbdde3b
MD5 7877d9ee57929afc3dc85347806225a0
BLAKE2b-256 f86c1e535045838664348fb3fb301764a67881c2de047d6b6fbab228f2f62cf2

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page