12

I am running an SQL query which returns a list of Decimals. When I try to convert this into JSON i get the type error.

The query:

res = db.execute("""
SELECT CAST ((SUM(r.SalesVolume)/1000.0) AS decimal(6,1))
FROM RawData r
INNER JOIN Product p
ON r.ProductId = p.ProductId 
INNER JOIN Calendar c
ON r.DayId = c.DayId
WHERE c.WeekCodeInYear BETWEEN 1 AND 12
AND 
c.YearId = 2014
GROUP BY c.WeekCodeInYear """)

Result List:

[Decimal('34.3'), Decimal('50.9'), Decimal('31.5'), Decimal('23.3'), Decimal('19
.7'), Decimal('56.9'), Decimal('43.8'), Decimal('35.2'), Decimal('29.2'), Decima
l('43.7'), Decimal('42.6'), Decimal('23.4')]

Code:

for row in res:
    testlist.append (row[0])
    print testlist

list = json.dumps(testlist)

And the I get the Unable to serialize error Tried looking up online, no much help. Please note that the final list would go as input data to a chart.

Tauseef Hussain
  • 1,049
  • 4
  • 15
  • 29

2 Answers2

22

Use a override default:

import json
from decimal import Decimal

def default(obj):
    if isinstance(obj, Decimal):
        return str(obj)
    raise TypeError("Object of type '%s' is not JSON serializable" % type(obj).__name__)

json.dumps(testlist, default=default)

Or just do str on Decimal object:

for row in res:
    testlist.append (str(row[0]))
json.dumps(testlist)
doekman
  • 18,750
  • 20
  • 65
  • 86
LittleQ
  • 1,860
  • 1
  • 12
  • 14
  • 1
    the latter one is more simple. – LittleQ Jul 03 '15 at 09:42
  • I found extending the default kind of tricky, and I found someone's library doing it for more types in an organized fashion. https://pypi.org/project/json-default/ Also I wanted to store mine as a float, so I changed your line 6 to `float(obj)` . – phyatt Mar 18 '21 at 16:18
  • Also like in the linked example at the top `SimpleJson` supports `Decimal` nicely. https://github.com/simplejson/simplejson – phyatt Mar 18 '21 at 16:21
10

As the error says, the Decimal type is not able to be serialized directly into JSON. Considering casting the Decimal into a float if you wish to keep that as a number, however you may get rounding errors. i.e.

for row in res:
    testlist.append(float(row[0]))

Or alternatively build the list using list comprehension, this time I cast to str.

testlist = [str(row[0]) for row in res]

The latter is an appropriate representation as the Decimal type can be unambiguously represented by str. You can grab the original value like so

from decimal import Decimal
jlist = json.dumps(testlist)  # don't use list as it's predefined type
new_list = json.loads(jlist)
new_dlist = [Decimal(s) for s in new_list]

new_dlist should be identical to the original templist.

metatoaster
  • 17,419
  • 5
  • 55
  • 66
  • If the input is already within 1 decimal digit _always_ then you will always have that precision. Extracting that back out will require constructing the `Decimal` with the correct amount of precision. – metatoaster Jul 03 '15 at 09:42