Skip to main content

Canonical JSON hashing and JSON-safe serialization with pluggable handlers and configurable limits.

Project description

dr-serialize

JSON-safe serialization and canonical hashing for Python: two deliberately separate lanes - a policy-driven normalization lane and a strict identity lane - plus general-purpose canonical JSON utilities:

                normalization lane (policy)
Any value --> Serializer.to_jsonable(...) --> diagnostic normalized JSON
                                                   |
                canonical JSON (deterministic)     v
              canonical_json(...) --> stable text --> json_hash(...)

                identity lane (strict, policy-free)
Raw mapping --> IdentityDocument --> canonical_identity_json --> identity_document_hash
  • The normalization lane is policy: it decides what your objects become as diagnostic normalized JSON - extensible via handlers, bounded by explicit limits, lossy where it must be.
  • The canonical JSON utilities are deterministic: they encode already-JSON-safe data as canonical text and hashes - no handlers, no limits, same input, same bytes, forever.
  • The identity lane is strict: it validates strict JSON and the exact Identity Document shape, then hashes the canonical bytes - no coercion, and diagnostic normalized JSON never feeds it.

The vocabulary sheet (source: .defs/vocab.html) is the authoritative statement of the identity contract this repo implements: the terms, the guarantees, what is in and out of scope, and the mapping from each term to the exported names.

Normalization and canonical JSON compose at your call site, so hashes never silently depend on serialization policy:

hash_value = json_hash(serializer.to_jsonable(value))

Ecosystem

dr-serialize provides serialization/schema utilities shared across the dr-* stack: JSON-safe conversion, explicit limits, and canonical hashing. Neighbor repos are dr-providers, dr-graph, dr-platform, dr-code, whetstone-ai, and unitbench. This repo depends directly on pydantic and no named ecosystem neighbor; no consumer repo is declared here, though tests document extraction lineage from whetstone-ai.

Normalization: Serializer

from dr_serialize import Serializer, postgres_jsonb_limits

serializer = Serializer(limits=postgres_jsonb_limits())
payload = serializer.to_jsonable(anything)   # JSON-safe, size- and depth-checked

A Serializer bundles SerializationLimits with an ordered tuple of handlers. Built-in handlers cover scalars, sequences, mappings, bytes, types, Pydantic models, coroutines/generators, and __dict__ objects. Your handlers run after the scalar/container built-ins and before the fallbacks, so they can intercept any non-primitive value:

from dr_serialize import ConversionContext, JsonableHandle, Serializer, postgres_jsonb_limits

def jsonable_point(x: object, ctx: ConversionContext) -> JsonableHandle:
    if isinstance(x, Point):
        return True, {"x": ctx.convert(x.x, "x"), "y": ctx.convert(x.y, "y")}
    return False, None      # fall through to the next handler

serializer = Serializer(limits=postgres_jsonb_limits(), handlers=(jsonable_point,))

Handlers recurse through ctx.convert(child, key) - the library owns depth and path bookkeeping, and the configured max_depth is enforced through handler recursion too.

Limits

SerializationLimits (frozen) carries max_depth, max_bytes, and hard_max_bytes. postgres_jsonb_limits(max_bytes=...) is the shipped preset for Postgres JSONB storage; construct your own for other ceilings. to_jsonable requires limits explicitly - every call site states its storage policy.

Canonical JSON: canonical_json and json_hash

from dr_serialize import canonical_json, json_hash

text = canonical_json(payload)                     # sorted keys, compact, NaN rejected
key  = json_hash(payload, length=16)               # truncated hex hash

Both take Jsonable input - data that is already JSON-safe, typically the output of Serializer.to_jsonable or values you construct yourself. These utilities are intentionally policy-free: hashes are long-lived keys, so they must never change because a handler was added or a limit tuned. If you need conversion first, compose with the normalization lane explicitly.

Identity lane: Identity Document and identity_document_hash

The identity lane is the strict, policy-free path for cross-repo domain identity: validate strict JSON, wrap it in the exact three-field Identity Document {schema, schema_version, payload}, render its Canonical Identity JSON, and hash the canonical bytes into the full Identity Hash. The vocabulary sheet defines each term and the guarantees that bind this lane; the owning domain chooses the schema, version, and complete payload - dr-serialize only validates.

from dr_serialize import build_identity_document, identity_document_hash

doc = build_identity_document(
    schema="example.config",       # the owning domain chooses this
    schema_version=1,              # ...and this
    payload={"identity_field": "value"},  # ...and the complete payload
)
h = identity_document_hash(doc)    # full 64-char lowercase SHA-256 hex

Rejections are typed: StrictJsonError for values that are not strict JSON, IdentityDocumentError for documents that are not the exact three-field shape. There is no truncation parameter on this path; identity_hash_prefix is a separate, display-only helper that never establishes identity. Diagnostic normalized JSON never feeds identity hashing. Committed golden vectors live in tests/fixtures/identity_golden.json for dependent repos to reuse.

Errors

Both lanes and the canonical JSON utilities raise from one typed taxonomy rooted at SerializationError, and every error carries the path to the offending value plus a diagnostics() dict safe to persist:

Error Raised by
MaxDepthExceededError engine: nesting exceeded max_depth
JsonEncodeError engine probe and canonical JSON: value not JSON-encodable (canonical also rejects NaN/inf)
PayloadTooLargeError engine: encoded size exceeded max_bytes
ModelDumpError engine: Pydantic model_dump failed
ObjectVarsSerializationError engine: __dict__ walk failed
ValueTransformError base for consumer handler failures - subclass it with a message_prefix
StrictJsonError identity lane: value is not strict JSON (non-JSON, non-string key, NaN/inf, cycle)
IdentityDocumentError identity lane: document is not the exact three-field shape

API surface

Normalization: Serializer, ConversionContext, JsonableHandler, JsonableHandle, SerializationLimits, postgres_jsonb_limits, POSTGRES_JSONB_PAYLOAD_MAX_BYTES, POSTGRES_JSONB_MAX_BYTES. Canonical JSON: canonical_json, json_hash. Identity lane: validate_strict_json, IdentityDocument, build_identity_document, validate_identity_document, canonical_identity_json, identity_document_hash, compute_identity_hash, identity_hash_prefix, IDENTITY_DOCUMENT_FIELDS. Boundary type: Jsonable. Errors: the taxonomy above plus JsonPath, preview_repr, detail_repr.

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

dr_serialize-0.1.0.tar.gz (14.5 kB view details)

Uploaded Source

Built Distribution

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

dr_serialize-0.1.0-py3-none-any.whl (18.6 kB view details)

Uploaded Python 3

File details

Details for the file dr_serialize-0.1.0.tar.gz.

File metadata

  • Download URL: dr_serialize-0.1.0.tar.gz
  • Upload date:
  • Size: 14.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for dr_serialize-0.1.0.tar.gz
Algorithm Hash digest
SHA256 47863d076bc6a123dad5ff9f5e4005b5b69e2ddcc1824390bd71160d51d07bf5
MD5 90bc0ec729003b5e335c7c3fa340b008
BLAKE2b-256 c6258d4cdf3d0cd3dc4f8540bef72ab853ef12d4e34536f3c6dbb5381d95fb54

See more details on using hashes here.

Provenance

The following attestation bundles were made for dr_serialize-0.1.0.tar.gz:

Publisher: release.yml on danielle-rothermel/dr-serialize

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file dr_serialize-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: dr_serialize-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 18.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for dr_serialize-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b0e1110d5cf4fae346c04ad79b568c43c950e35cba71afb5af5f99f6262babc8
MD5 ca1225fcc91c3d3367452b263a36c861
BLAKE2b-256 371da6faf68c5a93a332279cce55db5ce2f0aa992f7a4727541ef7f6204eaf9e

See more details on using hashes here.

Provenance

The following attestation bundles were made for dr_serialize-0.1.0-py3-none-any.whl:

Publisher: release.yml on danielle-rothermel/dr-serialize

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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