Skip to content

feat: Add registered-type JSON pipeline for Durable Functions custom objects#336

Open
andystaples wants to merge 2 commits intoAzure:devfrom
andystaples:andystaples/durable-type-resolution-change
Open

feat: Add registered-type JSON pipeline for Durable Functions custom objects#336
andystaples wants to merge 2 commits intoAzure:devfrom
andystaples:andystaples/durable-type-resolution-change

Conversation

@andystaples
Copy link
Copy Markdown
Contributor

Summary

Refactors azure.functions._durable_functions to introduce an explicit registry for round-tripping user-defined classes through JSON, and reworks the existing _serialize_custom_object / _deserialize_custom_object helpers so that decoding is a pure data transformation (no module loading, no I/O).

What changed

  • New public API in azure.functions._durable_functions:
    • register_durable_serializable_type(cls) — opt-in registration (usable as a decorator) for classes that expose to_json / from_json.
    • to_json_string(obj) / from_json_string(s, *, accept_legacy=False) — symmetric encode/decode pipeline. Plain JSON values round-trip unchanged; dicts whose shape would collide with an internal marker are escaped on encode and restored on decode; only registered classes are reconstructed from __azfunc_obj__ markers.
  • Reworked back-compat shims:
    • _serialize_custom_object still emits the legacy __class__/__module__/__data__ shape, but now requires the class to be registered.
    • _deserialize_custom_object resolves target classes through the registry first, falling back to a hardened lookup in already-loaded modules (sys.modules only). The fallback requires a real type in the named module that defines to_json and a classmethod/staticmethod from_json. Unrecognised markers pass through as plain dicts.
  • New AZURE_FUNCTIONS_DURABLE_STRICT_LEGACY_DESERIALIZE env flag disables the sys.modules fallback for callers who want registry-only resolution.
  • Legacy decode emits a DeprecationWarning (resolved or not) so callers can migrate to the registry; the legacy shape will be removed in the next major.
  • OrchestrationContext and EntityContext are unchanged.

Tests

tests/test_durable_functions.py gains coverage for:

  • registry semantics (validation, idempotency, conflict detection),
  • symmetric round-trip over a corpus of plain JSON values, dicts containing reserved-key shapes, and nested collisions,
  • registered-instance round-trip (top-level and nested),
  • a _NoLazyImports context manager that asserts decode never calls importlib.import_module across every entry point and input shape,
  • the hardened resolver's accept/reject criteria (loaded vs unloaded module, instance-method from_json, missing to_json, non-class attribute, re-export with mismatched __module__),
  • legacy-shape decode behaviour with accept_legacy=True, including registry precedence, sys.modules fallback, strict mode, unloaded module, and the accept_legacy=False default,
  • legacy shim serialize behaviour and an end-to-end shim round-trip,
  • concurrent registration safety,
  • module surface (no top-level import_module binding).

Compatibility

  • No wire-format change. Existing producers continue to emit the legacy shape; existing in-flight history decodes through the back-compat shim or from_json_string(..., accept_legacy=True).
  • Apps that defined custom classes in modules imported at startup continue to round-trip without changes; a DeprecationWarning invites them to call register_durable_serializable_type(cls).
  • Apps relying on a class whose module had not yet been imported in the worker will see those values come back as dict (with a warning that explains how to opt in via the registry or by importing at startup).

Comment thread azure/functions/_durable_functions.py
@andystaples andystaples changed the title Add registered-type JSON pipeline for Durable Functions custom objects feat: Add registered-type JSON pipeline for Durable Functions custom objects Apr 28, 2026
@hallvictoria
Copy link
Copy Markdown
Contributor

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants