Self-describing JSON¶
A self-describing payload is one that names its own schema. didactic
uses content-addressed URIs of the form
didactic://v1/<structural-fingerprint>; a consumer that knows the
URI can look up the Theory and validate the payload without prior
knowledge of the producing class.
schema_uri¶
didactic.api.schema_uri returns the canonical URI for a Model class:
import didactic.api as dx
class User(dx.Model):
id: str
email: str
dx.schema_uri(User)
# 'didactic://v1/06ac976d...e8e780c3d76bf3bec5f81ab3591aadfacb'
The suffix is the structural fingerprint: two structurally identical Models share a URI regardless of class name.
embed_schema_uri¶
Wrap a Model dump with the $schema URI:
u = User(id="u1", email="ada@example.org")
payload = dx.embed_schema_uri(u)
# {
# '$schema': 'didactic://v1/06ac...',
# 'id': 'u1',
# 'email': 'ada@example.org',
# }
The $schema key is the first key in the resulting dict, which lets
JSON parsers that stream key-by-key dispatch on the schema before
processing the body.
FingerprintRegistry¶
didactic.api.FingerprintRegistry is the lookup side: register every Model your application understands, then the registry resolves a URI back to a class.
reg = dx.FingerprintRegistry()
reg.register(User)
dx.validate_with_uri_lookup(payload, reg)
# User(id='u1', email='ada@example.org')
validate_with_uri_lookup raises KeyError if the payload has no
$schema key, and LookupError if the URI is not registered.
When to use this¶
The pattern is suited to:
- Event buses where each message carries its own schema id.
- Long-term storage where readers may post-date writers.
- Federated deployments where different services know different Models, but agree on URIs through a registry.
For request/response APIs where the consumer already knows the
expected shape, regular JSON without the $schema field is enough.