Skip to content

Fix JSON serialization with pydantic >=2.12#1493

Open
michalkrompiec wants to merge 4 commits intoopenforcefield:mainfrom
michalkrompiec:fix/pydantic-212-serialize-as-any
Open

Fix JSON serialization with pydantic >=2.12#1493
michalkrompiec wants to merge 4 commits intoopenforcefield:mainfrom
michalkrompiec:fix/pydantic-212-serialize-as-any

Conversation

@michalkrompiec
Copy link
Copy Markdown

@michalkrompiec michalkrompiec commented Apr 28, 2026

Summary

pydantic 2.12 changed serialize_as_any=True to propagate the flag to all nested values (pydantic/pydantic#12348). This broke JSON serialization in two ways, requiring two fixes.

Fix 1: pydantic.py — remove serialize_as_any=True from model_dump_json()

With pydantic ≥2.12, serialize_as_any=True in JSON mode bypasses the Annotated WrapSerializer on _Quantity and tries to serialize pint.Quantity by its runtime type, which has no pydantic schema:

PydanticSerializationError: Unable to serialize unknown type: <class 'pint.Quantity'>

The WrapSerializer(quantity_json_serializer) on _Quantity handles JSON serialization correctly without this flag. serialize_as_any=True is intentionally kept in model_dump() (Python mode), which is unaffected by the pydantic 2.12 change.

Fix 2: components/potentials.py — add WrapSerializer to _AnnotatedCollections

Without serialize_as_any=True, pydantic serializes collections: dict[str, Collection] using the declared Collection base class schema, silently dropping subclass-specific fields (scale_14, cutoff, periodic_potential, etc. from _NonbondedCollection and subclasses). This caused wrong electrostatics energies after JSON roundtrip:

AssertionError: assert 0.5 == 0.8333333333  # scale_14 reset to default
EnergyError: {'Electrostatics': <Quantity(-17.38, 'kilojoule / mole')>}

The fix adds a WrapSerializer to _AnnotatedCollections that calls model_dump_json() on each collection instance directly. Since the call is on the actual instance (e.g. SMIRNOFFElectrostaticsCollection), pydantic uses the full subclass schema and all fields are preserved.

Fixes #1346.

Test results (pydantic 2.13.3, after both fixes, py312amber environment)

612 passed, 119 skipped, 13 xfailed, 8 xpassed in 248.98s

Zero failures. Mypy also passes (Success: no issues found in 85 source files).

pydantic 2.12 changed serialize_as_any=True to propagate the flag to all
nested values (pydantic/pydantic#12348). This causes model_dump_json() to
fail with "Unable to serialize unknown type: <class 'pint.Quantity'>" because
pydantic now bypasses the Annotated WrapSerializer on _Quantity and tries to
serialize pint.Quantity by its runtime type, which has no pydantic schema.

The fix removes serialize_as_any=True from model_dump_json(). The Annotated
WrapSerializer(quantity_json_serializer) on _Quantity correctly handles JSON
serialization without it. All 495 unit tests pass with pydantic 2.13.3.
@michalkrompiec michalkrompiec marked this pull request as ready for review April 28, 2026 14:38
@michalkrompiec
Copy link
Copy Markdown
Author

Oops, it turns out I have not run the tests properly locally. Will come back when fixed!

@michalkrompiec michalkrompiec marked this pull request as draft April 28, 2026 15:17
Without serialize_as_any=True, pydantic serializes collections using the
declared Collection base class schema, dropping subclass-specific fields
(scale_14, cutoff, periodic_potential, etc.) from _NonbondedCollection
and its subclasses. This caused silent data loss on JSON roundtrip,
producing wrong electrostatics energies after deserialization.

Fix by adding a WrapSerializer to _AnnotatedCollections that calls
model_dump_json() on each collection instance directly. Since the call
is made on the actual instance (e.g. SMIRNOFFElectrostaticsCollection),
pydantic uses the full subclass schema and all fields are preserved.
@michalkrompiec
Copy link
Copy Markdown
Author

This version passes the py312amber test on my machine. Can you try running the CI again, please?

@michalkrompiec michalkrompiec marked this pull request as ready for review April 28, 2026 15:31
@michalkrompiec michalkrompiec marked this pull request as draft April 28, 2026 15:44
- Remove pydantic <2.12 ceiling from docs_env.yaml
- Add ci-pydantic-latest.yaml workflow that upgrades pydantic to the
  latest release after pixi install and runs the full test suite

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@michalkrompiec michalkrompiec marked this pull request as ready for review April 28, 2026 15:46
@michalkrompiec
Copy link
Copy Markdown
Author

Added new pydantic to the environment and added a test explicitly using pydantic>2.12.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 28, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.73%. Comparing base (14cd931) to head (7f80e6b).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1493      +/-   ##
==========================================
- Coverage   94.07%   89.73%   -4.35%     
==========================================
  Files          73       73              
  Lines        6045     6048       +3     
==========================================
- Hits         5687     5427     -260     
- Misses        358      621     +263     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@michalkrompiec
Copy link
Copy Markdown
Author

Openeye tests failing due to lack of license keys - is it because the branch is on my account?

@michalkrompiec michalkrompiec changed the title Fix pint.Quantity JSON serialization with pydantic >=2.12 Fix JSON serialization with pydantic >=2.12 Apr 28, 2026
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.

Pydantic 2.12 breaks JSON serialization

2 participants