Skip to main content

Polymorphic serialization / deserialization for pydantic data classes

Project description

Polyserde

Introduction

polyserde solves a common problem when saving Pydantic models to JSON: it preserves the exact class types. When you use Pydantic's built-in model_dump(), it loses information about subclasses - so if you have a list of Animal objects that are actually Cat and Dog instances, the JSON won't remember which is which. polyserde embeds type information (like "__class__": "myapp.Cat") directly into the JSON, so when you load it back later, it automatically imports the right classes and reconstructs your exact object structure - no manual type tracking needed. It even checks if the library version you're loading with is compatible with the one that saved the file, warning you if there might be breaking changes (using semantic versioning rules). This makes it perfect for saving complex configuration objects, ML pipelines, or any nested data where preserving polymorphic types matters.

Features

  • Polymorphic type preservation — automatically remembers exact subclass types (e.g., Cat vs Dog when both inherit from Animal)
  • Automatic class imports — reconstructs objects by importing the right modules when deserializing
  • Semantic version checking — warns if the saved config may be incompatible with installed library versions
  • Supports complex Python types — handles enums, class references, and dictionaries with non-string keys
  • Human-readable JSON — produces self-describing, inspectable output that's easy to debug
  • Minimal dependencies — only requires pydantic ≥ 2.0 and packaging

Installation

pip install polyserde

Requires Python ≥ 3.9 and Pydantic ≥ 2.0.

Quick Start

Below is a complete usage example. Suppose the following classes are defined inside a module called zoolib.

# zoolib/__init__.py
from pydantic import BaseModel
from enum import Enum

class Species(Enum):
    CAT = "cat"
    DOG = "dog"

class Animal(BaseModel):
    name: str
    species: Species

class Cat(Animal):
    lives_left: int = 9

class Dog(Animal):
    breed: str
    is_good_boy: bool = True

class Zoo(BaseModel):
    location: str
    animals: list[Animal]
    caretaker_class: type = dict  # just an example class reference

Now use polyserde to serialize and deserialize the structure:

from zoolib import Zoo, Cat, Dog, Species
from polyserde import PolymorphicSerde

zoo = Zoo(
    location="Berlin",
    animals=[
        Cat(name="Mittens", species=Species.CAT, lives_left=7),
        Dog(name="Rex", species=Species.DOG, breed="Labrador"),
    ]
)

# Serialize to dict with metadata
data = PolymorphicSerde.dump(zoo, lib="zoolib", version="1.2.3")

# Save to JSON file
import json
with open("zoo_config.json", "w") as f:
    json.dump(data, f, indent=2)

# Load from JSON file
with open("zoo_config.json") as f:
    data = json.load(f)

# Deserialize (with version checking)
restored = PolymorphicSerde.load(data)
print(restored)
print(type(restored.animals[0]))

Output:

Zoo(location='Berlin', animals=[Cat(...), Dog(...)], caretaker_class=<class 'dict'>)
<class 'zoolib.Cat'>

If the current environment doesn’t have the same library version, polyserde emits helpful warnings such as:

⚠️ Major version mismatch for zoolib: serialized=1.2.3, installed=2.0.0 (config may be incompatible)

How It Works

PolymorphicSerde recursively converts complex Python objects into a JSON-safe structure with embedded type metadata:

  • Each Pydantic model is tagged with "__class__": "module.ClassName".
  • Enums are represented as "__enum__": "module.EnumClass.MEMBER".
  • Class references are stored as "__class_ref__": "module.Class".
  • Non-string dict keys are safely represented via { "__dict__": [{"__key__": ..., "value": ...}, ...]}.

This makes every JSON file self-describing — you can reload it anywhere, and PolymorphicSerde will reconstruct the correct objects automatically.

Version Safety

When saving a configuration, you can specify both the library name and version:

data = PolymorphicSerde.dump(my_config, lib="docling", version="0.14.0")

At load time, polyserde:

  • Looks up the installed version of the library,
  • Parses both versions semantically (using PEP 440),
  • Emits a warning if major or minor versions differ,
  • Falls back to strict equality for non-semantic versions.

Example warning:

⚠️ Minor version difference for docling: serialized=0.14.0, installed=0.15.0 (review config compatibility)

Contributing

Contributions are welcome! If you’d like to improve the serializer, add features, or extend compatibility, feel free to open a PR or issue.

  1. Fork the repo
  2. Create a feature branch
  3. Run tests (pytest)
  4. Submit a PR

Acknowledgments

Inspired by real-world serialization challenges in projects like Docling, FastAPI, and LangChain, where polymorphic configuration graphs are the norm.

polyserde brings predictable, portable, and version-safe serialization to any Pydantic-based system.

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

polyserde-0.1.1.tar.gz (5.5 kB view details)

Uploaded Source

Built Distribution

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

polyserde-0.1.1-py3-none-any.whl (6.3 kB view details)

Uploaded Python 3

File details

Details for the file polyserde-0.1.1.tar.gz.

File metadata

  • Download URL: polyserde-0.1.1.tar.gz
  • Upload date:
  • Size: 5.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.7.6

File hashes

Hashes for polyserde-0.1.1.tar.gz
Algorithm Hash digest
SHA256 c4b7a0ce2271721189719395011becc0b9e96594819b3f2115688c8892688881
MD5 c6c5c821355d26c0e61d7a47213771a6
BLAKE2b-256 ebd6043d03562a8345b3197e95b06297f430dab654ded910974cd01c713712ba

See more details on using hashes here.

File details

Details for the file polyserde-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: polyserde-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 6.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.7.6

File hashes

Hashes for polyserde-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 8c67e53498ef323fd4e1c3f632a72568e25b24cfaf7b041fb156175486768fcb
MD5 e0c3d89f82014ef2642f920840fbc98f
BLAKE2b-256 18095b4d800bd16d108101c038a64a4079051fca2858af1e068abb6e11464cb1

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