Skip to content

Decimal values lose precision during serialization — Decimal('19') becomes 19.0 in both model_dump() and model_dump_json() #203

@ralili

Description

@ralili

When a FHIR resource contains a Decimal value with no fractional part (e.g., Decimal('19')), both model_dump() and model_dump_json() serialize it as 19.0. This is problematic because FHIR assigns semantic meaning to decimal precision: 19, 19.0, and 19.00 are clinically distinct values.

Reproduction:

from decimal import Decimal
from fhir.resources.observation import Observation

observation_dict = {
    "resourceType": "Observation",
    "status": "final",
    "code": {"coding": [{"system": "http://loinc.org", "code": "12345-6"}]},
    "component": [
        {
            "code": {"coding": [{"system": "http://loinc.org", "code": "12345-6"}]},
            "valueQuantity": {
                "value": Decimal("19"),
                "unit": "mg/dL",
                "system": "http://unitsofmeasure.org",
                "code": "mg/dL",
            },
        }
    ],
}

observation = Observation(**observation_dict)

# Model stores the value correctly
print(repr(observation.component[0].valueQuantity.value))
# Output: Decimal('19')

# But both serialization methods convert to float
print(observation.model_dump_json())
# Output: ..."valueQuantity":{"value":19.0, ...

data = observation.model_dump()
print(type(data["component"][0]["valueQuantity"]["value"]))
# Output: <class 'float'>

Expected behavior:

Decimal('19') should serialize as 19 in JSON output, Decimal('19.0') as 19.0, etc. The original precision should be preserved.

Actual behavior:

All whole-number Decimal values are converted to float, resulting in a trailing .0. Additionally, model_dump() returns float instead of Decimal, making it impossible to recover the original precision downstream.

Impact:

  • FHIR precision semantics are lost. Per the FHIR spec, the number of significant digits conveys measurement precision.
  • Downstream FHIR consumers may interpret 19.0 differently from 19.
  • There is no clean workaround using model_dump() / model_dump_json() options (including round_trip=True and mode="json").

Suggested fix:

Add a custom field_serializer for DecimalType fields that preserves the original Decimal representation, e.g.:

from pydantic import field_serializer
from decimal import Decimal

@field_serializer('value', mode='plain')
@classmethod
def serialize_decimal(cls, v):
    if isinstance(v, Decimal):
        return int(v) if v == v.to_integral_value() else float(v)
    return v

Or ideally, a serializer that fully preserves FHIR precision semantics by writing the Decimal as a JSON number with the exact original significant digits.

Environment:

  • Python version: 3.12.7
  • pydantic version: 2.12.4
  • fhir.resources version: 8.1.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions