Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python 3.x cannot serialize Decimal() to JSON

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?

like image 564
LLL Avatar asked Apr 28 '17 11:04

LLL


People also ask

How do you serialize decimal objects in Python?

To JSON serialize a Decimal object with Python, we can use the json. dumps method from the simplejson module with use_decimal set to True .

Is not JSON serializable error in Python?

The Python "TypeError: Object of type method is not JSON serializable" occurs when we try to serialize a method to JSON. To solve the error, make sure to call the method and serialize the object that the method returns.


2 Answers

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!

like image 166
warrens Avatar answered Oct 01 '22 16:10

warrens


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.

like image 35
LLL Avatar answered Oct 01 '22 14:10

LLL