Python 3.x cannot serialize Decimal() to JSON

22,716

Solution 1

That answer turned out to be outdated and there was another answer with the working solution:

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

Note that this will convert the decimal to its string representation (e.g.; "1.2300") to a. not lose significant digits and b. prevent rounding errors.

Solution 2

I wanted to serialize Decimal to JSON, and also deserialize it back to actual Decimal class objects in a dictionary elsewhere.

Here is my sample program which works for me (Python 3.6):

import json
from decimal import Decimal
import decimal

class DecimalEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, decimal.Decimal):
            return {'__Decimal__': str(obj)}
        # Let the base class default method raise the TypeError
        return json.JSONEncoder.default(self, obj)

def as_Decimal(dct):
    if '__Decimal__' in dct:
        return decimal.Decimal(dct['__Decimal__'])
    return dct


sample_dict = {
        "sample1": Decimal("100"), 
        "sample2": [ Decimal("2.0"), Decimal("2.1") ],
        "sample3": Decimal("3.1415"),
        "other": "hello!"
    }
print("1. sample_dict is:\n{0}\n".format(sample_dict))

sample_dict_encoded_as_json_string = json.dumps(sample_dict, cls=DecimalEncoder)
print("2. sample_dict_encoded_as_json_string is:\n{0}\n".format(sample_dict_encoded_as_json_string))

sample_dict_recreated = json.loads(sample_dict_encoded_as_json_string, object_hook=as_Decimal)
print("3. sample_dict_recreated is:\n{0}\n".format(sample_dict_recreated))

And here is the output:

1. sample_dict is:
{'sample1': Decimal('100'), 'sample2': [Decimal('2.0'), Decimal('2.1')], 'sample3': Decimal('3.1415'), 'other': 'hello!'}

2. sample_dict_encoded_as_json_string is:
{"sample1": {"__Decimal__": "100"}, "sample2": [{"__Decimal__": "2.0"}, {"__Decimal__": "2.1"}], "sample3": {"__Decimal__": "3.1415"}, "other": "hello!"}

3. sample_dict_recreated is:
{'sample1': Decimal('100'), 'sample2': [Decimal('2.0'), Decimal('2.1')], 'sample3': Decimal('3.1415'), 'other': 'hello!'}

Hope this helps!

Share:
22,716
LLL
Author by

LLL

Software developer Interested in .net core, aws, distributed cloud-native systems Certified AWS Developer Associate

Updated on January 30, 2021

Comments

  • LLL
    LLL over 3 years

    I asked this question earlier, and it was marked as duplicate of this, but the accepted answer does not work and even pylint shows that there are errors in the code.

    What I want to do:

    from decimal import Decimal
    import json
    
    thang = {
        'Items': [{'contact_id': Decimal('2'), 'street_name': 'Asdasd'}, {'contact_id': Decimal('1'), 'name': 'Lukas', 'customer_id': Decimal('1')}],
         'Count': 2}
    
    print(json.dumps(thang))
    

    This throws: TypeError: Object of type 'Decimal' is not JSON serializable

    So I tried the linked answer:

    from decimal import Decimal
    import json
    
    thang = {
        'Items': [{'contact_id': Decimal('2'), 'street_name': 'Asdasd'}, {'contact_id': Decimal('1'), 'name': 'Lukas', 'customer_id': Decimal('1')}],
         'Count': 2}
    
    
    class DecimalEncoder(json.JSONEncoder):
        def _iterencode(self, o, markers=None):
            if isinstance(o, Decimal):
                # wanted a simple yield str(o) in the next line,
                # but that would mean a yield on the line with super(...),
                # which wouldn't work (see my comment below), so...
                return (str(o) for o in [o])
            return super(DecimalEncoder, self)._iterencode(o, markers)
    
    
    print(json.dumps(thang, cls=DecimalEncoder))
    

    And here the linter shows that line return super(DecimalEncoder, self)._iterencode(o, markers) has errors, because Super of 'DecimalEncoder' has no '_iterencode' member and when ran throws TypeError: Object of type 'Decimal' is not JSON serializable

    How do I make this work?

  • Cochise Ruhulessin
    Cochise Ruhulessin about 6 years
    This quite defeats the purpose of the decimal.Decimal class. It should serialize to a string instead of a float, since float is not accurate.
  • Zephaniah Grunschlag
    Zephaniah Grunschlag almost 4 years
    Totally agree. An answer that serializes a Decimal to a float should not be the top answer as it is dangerously inaccurate. For example, you can end up violating a legal contract because of a floating point rounding error.