How to Resolve Python "TypeError: Object of type Decimal is not JSON serializable"
When working with precise numerical data in Python using the decimal
module, you might encounter the TypeError: Object of type Decimal is not JSON serializable
error when trying to serialize data containing Decimal
objects into a JSON string using the standard json
library. This happens because the default JSON encoder doesn't natively recognize or know how to convert Decimal
objects.
This guide explains why this error occurs and details effective methods to serialize Decimal
objects correctly.
Understanding the Error: JSON Serializable Types
JSON (JavaScript Object Notation) has a limited set of standard data types: objects (maps/dictionaries), arrays (lists/tuples), strings, numbers (integers/floats), booleans (true
/false
), and null
. Python's built-in json
module knows how to convert standard Python types (dict
, list
, tuple
, str
, int
, float
, bool
, None
) into their corresponding JSON representations.
However, specialized types like decimal.Decimal
, datetime.datetime
, or custom class instances are not directly supported by the default encoder.
The Cause: json.dumps()
and Decimal
Objects
The TypeError
occurs because the json.dumps()
function (which serializes a Python object into a JSON formatted string) encounters a Decimal
object and doesn't have a built-in rule for converting it to a standard JSON type.
import json
from decimal import Decimal
# Create a Decimal object (preserves exact precision)
price = Decimal('19.99')
data = {'item': 'Book', 'price': price}
print(f"Data contains type: {type(data['price'])}") # Output: <class 'decimal.Decimal'>
try:
# ⛔️ TypeError: Object of type Decimal is not JSON serializable
# Default json.dumps doesn't know how to handle Decimal
json_output = json.dumps(data)
print(json_output)
except TypeError as e:
print(e)
Solution 1: Using the default
Argument with str
(Recommended for Precision)
The json.dumps()
function accepts a default
keyword argument. You can provide a function to default
that will be called for any object the encoder doesn't recognize. A simple and effective solution for Decimal
is to convert it to a string, which preserves its exact precision.
import json
from decimal import Decimal
price = Decimal('19.9900') # Note the trailing zeros
data = {'item': 'Book', 'price': price}
# ✅ Provide the built-in str function to the 'default' argument
json_output = json.dumps(data, default=str)
print(f"Serialized JSON (Decimal as string): {json_output}")
# Output: Serialized JSON (Decimal as string): {"item": "Book", "price": "19.9900"}
print(f"Output type: {type(json_output)}") # Output: <class 'str'>
default=str
: Whenjson.dumps
encounters theDecimal
objectprice
, it callsstr(price)
, which returns"19.9900"
. This string is JSON serializable.
This method guarantees that the exact decimal value, including trailing zeros indicating precision, is preserved in the JSON output as a string. The receiving system can then parse this string back into a high-precision decimal type if needed.
Solution 2: Creating a Custom JSONEncoder
Subclass
For more complex scenarios or reusable logic, you can create a custom encoder by subclassing json.JSONEncoder
and overriding its default()
method.
import json
from decimal import Decimal
class DecimalEncoder(json.JSONEncoder):
def default(self, obj):
# Check if the object is an instance of Decimal
if isinstance(obj, Decimal):
# Convert Decimal to string to preserve precision
return str(obj)
# Let the base class default method handle other types
# or raise a TypeError for unsupported types.
return super(DecimalEncoder, self).default(obj)
# Example usage
price = Decimal('0.1') # A value often imprecise as float
inventory = [Decimal('1.5'), Decimal('2.0')]
data = {'cost': price, 'stock': inventory}
# ✅ Use the custom encoder via the 'cls' argument
json_output = json.dumps(data, cls=DecimalEncoder)
print(f"Serialized JSON (Custom Encoder): {json_output}")
# Output: Serialized JSON (Custom Encoder): {"cost": "0.1", "stock": ["1.5", "2.0"]}
- The
default(self, obj)
method receives objects the standard encoder can't handle. - We check if
obj
is aDecimal
. If so, we returnstr(obj)
. - Otherwise, we call the parent class's
default
method (super().default(obj)
) to handle standard types or raise the appropriateTypeError
for other unhandled types. - You pass your custom class to
json.dumps()
using thecls
argument:cls=DecimalEncoder
.
Solution 3: Using the simplejson
Library
The third-party simplejson
library is an alternative JSON encoder/decoder that offers more features, including native support for Decimal
objects (often enabled by default).
- Install
simplejson
:pip install simplejson
# Or: python -m pip install simplejson - Use
simplejson.dumps()
:from decimal import Decimal
# Import simplejson, often aliased as json for compatibility drop-in
import simplejson as json
price = Decimal('19.99')
data = {'item': 'Book', 'price': price}
# simplejson handles Decimal natively (usually as a number/float)
# The use_decimal=True argument ensures Decimal serialization (often the default)
json_output = json.dumps(data, use_decimal=True)
print(f"Serialized JSON (simplejson): {json_output}")
# Output: Serialized JSON (simplejson): {"item": "Book", "price": 19.99}
print(f"Output type: {type(json_output)}") # Output: <class 'str'>
# Note: The 'price' value in the JSON is now a number, not a string.
simplejson
often serializes Decimal
directly as a JSON number (which might be interpreted as a float by the receiver). This is convenient but might lead to precision loss if the receiver doesn't handle it carefully (see next section).
Important Consideration: String vs. Float for Precision
- Why
str
? (Solutions 1 & 2):decimal.Decimal
is used for exact precision, which standard floating-point numbers often lack (e.g.,0.1 + 0.2
is not exactly0.3
in binary floating-point). JSON's number type is typically implemented using floats. Serializing aDecimal
as a string ("19.9900"
) guarantees that the exact value is transmitted without potential floating-point rounding errors. The receiving system must then be aware that this string represents a decimal and parse it accordingly. This is the standard way to exchange high-precision decimal values via JSON. - Why
float
? (simplejson
default): If the precision loss associated with standard floats is acceptable for your application, or if the receiving system expects JSON numbers directly, usingsimplejson
(or potentiallydefault=float
injson.dumps
, though less common forDecimal
) might be simpler. However, you lose the guarantee of exact precision.
Conclusion
The TypeError: Object of type Decimal is not JSON serializable
occurs because Python's default json
encoder doesn't natively support the decimal.Decimal
type.
The recommended solutions are:
- Use
json.dumps(data, default=str)
: This convertsDecimal
objects to strings, preserving their exact precision, which is crucial for financial or scientific data. This is generally the best practice. - Create a custom
JSONEncoder
subclass: Override thedefault
method to convertDecimal
tostr
. This is useful for reusable or more complex serialization logic. - Use the
simplejson
library: It offers nativeDecimal
support (usually serializing as a JSON number/float), which might be convenient if exact precision is less critical or if the receiver expects numbers.
Choose the method based on whether preserving the exact precision of the Decimal
(by converting to a string) is required for your application.