15

Lambda execution failed with status 200 due to customer function error: Object of type 'Decimal' is not JSON serializable

I went through all the existing solutions in the following link but nothing worked for me. What am I doing wrong?: Python JSON serialize a Decimal object

import json
import boto3
import decimal


client = boto3.resource('dynamodb')
table = client.Table('table')

def lambda_handler(event, context):
    method = event["httpMethod"]
    print(event)
    if method=="POST":
        return POST(event)
    elif method=="DELETE":
        return DELETE(event)
    elif method=="GET":
        return GET(event)

#the respons format
def send_respons(responseBody, statusCode):
    response = {
        "statusCode": statusCode,
        "headers": {
            "my_header": "my_value"
        },
        "body": json.dumps(responseBody),
        "isBase64Encoded": 'false'
    }
    return response
    

def GET(event):
    tab = table.scan()['Items']
    ids = []
            for item in tab:
                    ids.append({"id":item["id"], "decimalOBJ":decimal.Decimal(item["decimalOBJ"]}))
            return send_respons(ids, 201)

4 Answers 4

34

Here is an example of extending the JSONEncoder to handle Decimal type also specified in the json docs

from decimal import Decimal

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

Call it using

json.dumps(some_object, cls=DecimalEncoder)

By converting to a str a good degree of precision will be maintained, without relying on external packages.

Sign up to request clarification or add additional context in comments.

1 Comment

You might want to try return str(obj.normalize()) instead. See docs.python.org/library/decimal.html#decimal.Decimal.normalize for details on decimal normalization.
19

It seems you have two options:

  1. Probably easiest, you can serialize the int/float value of a Decimal object:

""" assume d is your decimal object """

serializable_d = int(d) # or float(d)

d_json = json.dumps(d)

  1. You can add simplejson to your requirements.txt, which now has support for serializing Decimals. It's a drop-in replacement for the included json module.
import simplejson as json # instead of import json

The rest of your code will work the same. If you need further assistance, kindly leave a comment.

5 Comments

Decimal objects exist specifically for accuracy; casting to float has the potential to add uncertainty. I'd go with a string value before converting a Decimal to another type.
True, and the json serialization automatically converts the value to a string. But fair point, nonetheless.
If you know it is precise to cents, you can exactly send it to js by scaling it up by 100 as a decimal, then truncate to bigint, as long as you stay <= +/- 2**53, it will be perfectly exact for integers when js puts it in a double. Then divide by 100 and .toFixed(2) at the last moment in js and it'll work as intended.
then the next Exception: <class 'TypeError'>: Object of type date is not JSON serializable.
option 2 only. I don't recommend Option 1 at all.
6

Create a function to handle the TypeError and use default argument of json.dumps to set this. This avoid TypeError: Object of type Decimal is not JSON serializable.

https://docs.python.org/3/library/json.html

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.

json_utils.py:

import decimal
import json


def dumps(item: dict) -> str:
    return json.dumps(item, default=default_type_error_handler)


def default_type_error_handler(obj):
    if isinstance(obj, decimal.Decimal):
        return int(obj)
    raise TypeError

test_json_utils.py:

import json
from decimal import Decimal
from commons import json_utils


def test_different_data_types():
    # Prepare data
    item = {
        "a-string": "lorem",
        "a-boolean": True,
        "a-number": 4711,
        "a-decimal-object": Decimal(4711)  # Used by dynamoDb boto3 client
    }

    # Execute
    item_as_json = json_utils.dumps(item)

    # Assert
    item_back_to_dict = json.loads(item_as_json)
    assert item_back_to_dict == item

Comments

1

Creating a class for adjusting JSON encoding, seems too much, at least for this case in particular.

Use int as default function, then dumps will display as int, the objects that can't be serialized:

json.dumps(response, default=int)

More information about the approach is described here.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.