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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions