Skip to content

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

Description

@Binilkks

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions