Python Serverless on AWS Lambda · APIs and Events in Python

JSON Responses: Decimals, Dates and Errors

Why reading a number from DynamoDB crashes the API with a 502, and how to return responses reliably.

Scenario

Example app: GET /products/{id} reads an item from DynamoDB with boto3 and returns it as JSON through API Gateway.

What you see in production

Every request for a product returns 502 Bad Gateway. Logs show TypeError: Object of type Decimal is not JSON serializable. A similar endpoint that returns order timestamps fails with datetime is not JSON serializable.

The naive code

import json

def handler(event, context):
    item = table.get_item(Key={"id": event["pathParameters"]["id"]})["Item"]   # KeyError if missing
    return {"statusCode": 200, "body": json.dumps(item)}                         # Decimal -> TypeError

Reproduce it

Serialising DynamoDB itemsTry it yourself
import json
from datetime import datetime, timezone
from decimal import Decimal

item = {"id": "p-1", "name": "Mug", "price": Decimal("12.50"), "stock": Decimal("7"),
        "updated": datetime(2026, 10, 19, 9, 30, tzinfo=timezone.utc)}

try:
    json.dumps(item)
except TypeError as e:
    print("naive:", e)

def default(o):
    if isinstance(o, Decimal):
        return int(o) if o == o.to_integral_value() else str(o)   # keep money exact as a string
    if isinstance(o, datetime):
        return o.isoformat()
    raise TypeError(f"{type(o).__name__} is not JSON serializable")

print("fixed:", json.dumps(item, default=default))

Output

naive: Object of type Decimal is not JSON serializable
fixed: {"id": "p-1", "name": "Mug", "price": "12.50", "stock": 7, "updated": "2026-10-19T09:30:00+00:00"}

Root cause

boto3 returns DynamoDB numbers as decimal.Decimal to preserve precision, and application code often adds datetime values. The standard json module cannot serialise either, so the handler raises, and API Gateway reports a generic 502 because the function did not return a valid response. A missing item (KeyError on "Item") fails the same way.

The fix

Use Powertools for AWS Lambda (Python) event handler, which routes requests, serialises responses (handling Decimal), and maps exceptions to proper HTTP responses:

from aws_lambda_powertools.event_handler import APIGatewayHttpResolver
from aws_lambda_powertools.event_handler.exceptions import NotFoundError

app = APIGatewayHttpResolver()

@app.get("/products/<product_id>")
def get_product(product_id: str):
    item = table.get_item(Key={"id": product_id}).get("Item")
    if item is None:
        raise NotFoundError(f"product {product_id} not found")        # 404, not 502
    return item

def handler(event, context):
    return app.resolve(event, context)

Decide how money is represented in your API (string decimals or integer minor units) rather than converting to float, which loses precision.

Trade-offs

  • A framework adds a little init time; for one-route functions, a small encoder is enough.
  • Converting Decimal to float is convenient but can show 12.499999 style values and break money calculations.
  • Returning str(Decimal) changes the JSON type to string; document it for API clients.

Detect it in production

  • 502 responses with TypeError ... not JSON serializable in logs.
  • Contract tests that call each route with realistic items, including numbers, dates and missing items.
  • Alarms on 5xx per route.

Key points

  • boto3 returns Decimal for numbers; json.dumps cannot serialise it (or datetime).
  • Use a custom encoder or Powertools' event handler.
  • Map missing items and validation failures to 4xx instead of crashing with 502.

The same error-mapping principles are covered in depth in the Node.js course.

Further reading: Powertools for AWS Lambda (Python): REST API event handler · Python docs: json

Practice questions

Why does returning a DynamoDB item with json.dumps fail in a Python Lambda function?

Answer

boto3 deserialises DynamoDB numbers as decimal.Decimal, which the standard json module cannot serialise, so json.dumps raises TypeError and API Gateway returns a 502.

Why not simply convert Decimal values to float?

Answer

Floats are binary approximations, so money and other exact values can change (for example 0.1 + 0.2). Represent such values as strings or integers in minor units, and convert deliberately.

What does Powertools' API Gateway event handler add over a plain handler?

Answer

Routing by method and path, request parsing helpers, JSON serialisation that handles common types, exception-to-status mapping (for example NotFoundError to 404), CORS and middleware, keeping handlers small.

Open in the app to mark lessons complete, take notes and track your progress.