13

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)
Mig B
  • 637
  • 1
  • 11
  • 19
papi
  • 227
  • 1
  • 4
  • 11

3 Answers3

25

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.

tver3305
  • 8,224
  • 2
  • 21
  • 23
11

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.

hd1
  • 33,938
  • 5
  • 80
  • 91
  • 4
    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. – kungphu Aug 06 '20 at 08:07
  • 1
    True, and the json serialization automatically converts the value to a string. But fair point, nonetheless. – hd1 Aug 06 '20 at 08:16
  • 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. – doug65536 Nov 25 '21 at 03:59
  • then the next Exception: : Object of type date is not JSON serializable. – Max Kleiner Dec 05 '21 at 11:08
5

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
Sma Ma
  • 3,343
  • 2
  • 31
  • 39