Python Lambda Function Parsing DynamoDB’s to JSON

I develop a AWS Lambda in Python. I read data in dynamoDB but after I want return json data to APIGateway.

print(dynamodbTableQueryData)
#{'year': Decimal('2015'), 'info': {'rating': Decimal('0'), 'plot': 'Nothing happens at all.'}, 'title': 'The Big New Movie'}

 return {
    'statusCode': 200,
    'body': json.dumps(dynamodbTableQueryData)
}

if I write this, the title is returned on APIGateway:

    return {
        ​'statusCode': 200,
        ​'body': json.dumps(dynamodbTableQueryData['title'])
   ​ }

if I write this, the year is not returned on APIGateway and I have an error:

    return {
        ​'statusCode': 200,
        ​'body': json.dumps(dynamodbTableQueryData['year'])
   ​ }

this error:

[ERROR] TypeError: Object of type Decimal is not JSON serializable Traceback (most recent call last)

I try this but do not work:

import boto3
from boto3.dynamodb.types import TypeSerializer, TypeDeserializer
ts= TypeSerializer()
td = TypeDeserializer()

deserialized_data= td.deserialize(dynamodbTableQueryData)
print(deserialized_data)

Answer

It is because JSON object must only contain specific types such as list, dict, str, int, float, etc. Thus for the types that are not supported such as Decimal, datetime, etc., we should explicitly define how should they be parsed by defining a custom serializer. For example, should Decimal('2015') be a string "Decimal(2015)" or just the number part "2015" or should it be an int 2015 or float 2015.0 or something else.

Here is a solution that also works recursively if the Decimal object is in an inner nest. We will get the string value of the number e.g. "2015" (I chose str type because returning float might be inconsistent depending on the tech stack e.g. 1.2 might be interpreted as 1.20000001). This will be implemented via the default and cls argument to json.dumps() which as documented:

If specified, default should be a function that gets called for objects that can’t otherwise be serialized. It should return a JSON encodable version of the object or raise a TypeError. If not specified, TypeError is raised.

from decimal import Decimal
import json

dynamodbTableQueryData = {'year': Decimal('2015'), 'info': {'rating': Decimal('0'), 'plot': 'Nothing happens at all.'}, 'title': 'The Big New Movie'}

# Solution 1
def to_serializable(val):
    if isinstance(val, Decimal):
        return str(val)
    return val

print("Solution 1")
result = json.dumps(dynamodbTableQueryData['info'], default=to_serializable)
print(result)
result = json.dumps(dynamodbTableQueryData['title'], default=to_serializable)
print(result)
result = json.dumps(dynamodbTableQueryData['year'], default=to_serializable)
print(result)

# Solution 2

class MyJSONEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, Decimal):
            return str(obj)
        else:
            return super().default(obj)

print("Solution 2")
result = json.dumps(dynamodbTableQueryData['info'], cls=MyJSONEncoder)
print(result)
result = json.dumps(dynamodbTableQueryData['title'], cls=MyJSONEncoder)
print(result)
result = json.dumps(dynamodbTableQueryData['year'], cls=MyJSONEncoder)
print(result)

Output

Solution 1
{"rating": "0", "plot": "Nothing happens at all."}
"The Big New Movie"
"2015"
Solution 2
{"rating": "0", "plot": "Nothing happens at all."}
"The Big New Movie"
"2015"