decimal.Decimal not handled in preconf converters (json, pyyaml)

Open Beginner friendly
#761 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
72/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Quiet
Tech stack
json, python, yaml
Domain
tooling

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.

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 — NOT float, which loses precision for values like Decimal('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
  • cattrs latest
  • Python 3.12
Dominant language
Python
Stars
1.1k
Forks
159
Avg merge
12h 21m
Merged PRs (30d)
6

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from python-attrs/cattrs

All issues in python-attrs/cattrs

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.