python-attrs / python-attrs/cattrs
decimal.Decimal not handled in preconf converters (json, pyyaml)
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 1.1k
- Forks
- 159
- Avg merge
- 12h 21m
- Merged PRs (30d)
- 6
Description
Bug: decimal.Decimal crashes in preconf converters
Problem
decimal.Decimal is a very common stdlib type in financial and e-commerce applications, but none of the preconf converters handle it. Both unstructure and structure operations fail.
Reproduction
import decimal
import attrs
from cattrs.preconf.json import make_converter
@attrs.define
class Order:
amount: decimal.Decimal
conv = make_converter()
o = Order(amount=decimal.Decimal('19.99'))
# Unstructure passes through the raw Decimal object — not JSON serializable
conv.dumps(o)
# → TypeError: Object of type Decimal is not JSON serializable
# Structure fails entirely
conv.structure({'amount': '19.99'}, Order)
# → StructureHandlerNotFoundError: Unsupported type: <class 'decimal.Decimal'>. Register a structure hook for it.
Same crash with the pyyaml preconf:
from cattrs.preconf.pyyaml import make_converter
conv = make_converter()
conv.dumps(Order(amount=decimal.Decimal('19.99')))
# → RepresenterError: cannot represent an object: Decimal('19.99')
Expected Behavior
The json and pyyaml preconf converters should handle decimal.Decimal out of the box:
- Unstructure:
Decimal → str(preserving full precision — NOTfloat, which loses precision for values likeDecimal('0.1')) - Structure:
str → Decimal
conv.dumps(Order(amount=Decimal('19.99')))
# → '{"amount": "19.99"}'
conv.loads('{"amount": "19.99"}', Order)
# → Order(amount=Decimal('19.99'))
Why str and not float
Using float(Decimal('19.99')) introduces floating-point precision loss:
>>> float(Decimal('19.99'))
19.99 # looks ok
>>> float(Decimal('0.1')) + float(Decimal('0.2'))
0.30000000000000004 # precision lost
Serializing as a string preserves the exact value and is the standard practice for monetary amounts.
Suggested Fix
In cattrs/preconf/json.py and cattrs/preconf/pyyaml.py configure_converter:
from decimal import Decimal
converter.register_unstructure_hook(Decimal, str)
converter.register_structure_hook(Decimal, lambda v, _: Decimal(v))
Environment
cattrslatest- Python 3.12
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in configure_converter in cattrs/preconf/json.py and cattrs/preconf/pyyaml.py, then run the Decimal reproduction shown in the issue for both preconfs. Done means unstructure emits Decimal as a precise string and structure rebuilds Decimal values, with the JSON and PyYAML round trips succeeding.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- json, python, yaml
- Domain
- tooling
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100