341

I have a Decimal('3.9') as part of an object, and wish to encode this to a JSON string which should look like {'x': 3.9}. I don't care about precision on the client side, so a float is fine.

Is there a good way to serialize this? JSONDecoder doesn't accept Decimal objects, and converting to a float beforehand yields {'x': 3.8999999999999999} which is wrong, and will be a big waste of bandwidth.

dreftymac
  • 31,404
  • 26
  • 119
  • 182
Knio
  • 6,638
  • 3
  • 29
  • 29
  • 3
    related Python bug: [json encoder unable to handle decimal](http://bugs.python.org/issue16535) – jfs Jul 27 '14 at 14:04
  • 3.8999999999999999 is no more wrong than 3.4 is. 0.2 has no exact float representation. – Jasen May 25 '15 at 03:55
  • 9
    @Jasen 3.89999999999 is about 12.8% more wrong than 3.4 is. The JSON standard is only about serialisation and notation, not implementation. Using IEEE754 is not part of the raw JSON spec, it is only the most common way to implement it. An implementation that uses only precise decimal arithmetic is completely (in fact, even more strictly) conforming. – hraban May 07 '20 at 10:57
  • 2
    _less_ wrong. ironic. – hraban May 07 '20 at 12:10

24 Answers24

271

Simplejson 2.1 and higher has native support for Decimal type:

>>> json.dumps(Decimal('3.9'), use_decimal=True)
'3.9'

Note that use_decimal is True by default:

def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True,
    allow_nan=True, cls=None, indent=None, separators=None,
    encoding='utf-8', default=None, use_decimal=True,
    namedtuple_as_object=True, tuple_as_array=True,
    bigint_as_string=False, sort_keys=False, item_sort_key=None,
    for_json=False, ignore_nan=False, **kw):

So:

>>> json.dumps(Decimal('3.9'))
'3.9'

Hopefully, this feature will be included in standard library.

Jonah Bishop
  • 12,279
  • 6
  • 49
  • 74
Lukas Cenovsky
  • 5,476
  • 2
  • 31
  • 39
  • 11
    Hmm, for me this converts Decimal objects to floats, which is not acceptable. Loss of precision when working with currency, for instance. – Matthew Schinckel Oct 22 '10 at 00:12
  • 15
    @MatthewSchinckel I think it doesn't. It actually makes a string out of it. And if you feed the resultant string back to `json.loads(s, use_decimal=True)` it gives you back the decimal. No float in entire process. Edited above answer. Hope original poster is fine with it. – Shekhar Nov 22 '11 at 10:55
  • 2
    Aha, I think I was not using `use_decimal=True` on the loads, too. – Matthew Schinckel Nov 23 '11 at 23:04
  • 3
    For me `json.dumps({'a' : Decimal('3.9')}, use_decimal=True)` gives `'{"a": 3.9}'`. Was the goal not `'{"a": "3.9"}'` ? – MrJ May 02 '14 at 17:43
  • @MrJ `"3.9"` is a string, `Decimal('3.9')` is a number, so `{"a": 3.9}` is what is expected. – vikki Jun 02 '14 at 23:05
  • This is much better than using the string version of the `Decimal`. – varatis Jun 23 '14 at 19:10
  • 5
    `simplejson.dumps(decimal.Decimal('2.2'))` also works: no explicit `use_decimal` (tested on simplejson/3.6.0). Another way to load it back is: `json.loads(s, parse_float=Decimal)` i.e., you can read it using stdlib `json` (and old `simplejson` versions are also supported). – jfs Jul 27 '14 at 14:04
  • 1
    TypeError: __init__() got an unexpected keyword argument 'use_decimal' – Yuseferi Jul 17 '18 at 06:44
  • @Madeo I have the same issue, python 3.7 i think its because the json an simplejson libraries are different – Sidharth Ghoshal Jul 15 '20 at 03:55
  • I prefer the standard library `default` hook here to handle Decimal as it _makes it explicit how you are handling these_. You can't just assume that the decoder (which might not be another Python app) can handle the higher precision values, you may want to encode to a `{"type": "__decimal__", "value", "string representation of decimal"}` object, for example. These decisions are _domain specific_, because you are stepping outside the confines of the JSON standard. – Martijn Pieters Mar 10 '21 at 10:44
  • @Martjin does the standard library have a means for using `dumps` with Decimal? The 3.11 json module doc shows an float keyword for `loads` only – Mike May 08 '23 at 02:29
264

I would like to let everyone know that I tried Michał Marczyk's answer on my web server that was running Python 2.6.5 and it worked fine. However, I upgraded to Python 2.7 and it stopped working. I tried to think of some sort of way to encode Decimal objects and this is what I came up with:

import decimal

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.

This should hopefully help anyone who is having problems with Python 2.7. I tested it and it seems to work fine. If anyone notices any bugs in my solution or comes up with a better way, please let me know.

Usage example:

json.dumps({'x': decimal.Decimal('5.5')}, cls=DecimalEncoder)
David Schumann
  • 13,380
  • 9
  • 75
  • 96
Elias Zamaria
  • 96,623
  • 33
  • 114
  • 148
  • 4
    Python 2.7 changed the rules for rounding floats so this works. See discussion in http://stackoverflow.com/questions/1447287/format-floats-with-standard-json-module – Nelson Apr 06 '11 at 23:32
  • 3
    For those of us who can't use simplejson (ie. on Google App Engine) this answer is a Godsend. – Joel Cross Oct 28 '13 at 15:42
  • I guess it depends on platform. I'm seeing `{"x": 54.399999999999999}`. Yes, it did not results in error but it is not what was intended. – some user Mar 09 '16 at 01:43
  • 24
    Use `unicode` or `str` instead of `float` to ensure precision. – Seppo Erviälä Aug 30 '16 at 13:03
  • 3
    The problem with 54.3999... was important in Python 2.6.x and older where the conversion float to string did not worked regularly, but the conversion Decimal to str is much more incorrect because it would be serialized as string with double quotes `"54.4"`, not as a number. – hynekcer Jun 16 '17 at 20:25
  • 2
    Works in python3 – SeanFromIT Jun 07 '18 at 16:10
  • 1
    what 'type' does this return? I'm getting a str type, which isn't ideal – Brendan Metcalfe Aug 08 '19 at 04:30
182

How about subclassing json.JSONEncoder?

class DecimalEncoder(json.JSONEncoder):
    def default(self, o):
        if isinstance(o, decimal.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).default(o)

Then use it like so:

json.dumps({'x': decimal.Decimal('5.5')}, cls=DecimalEncoder)
Dewald Abrie
  • 1,392
  • 9
  • 21
Michał Marczyk
  • 83,634
  • 13
  • 201
  • 212
  • Simplejson has native support for Decimals - see my answer: http://stackoverflow.com/questions/1960516/python-json-serialize-a-decimal-object/3148376#3148376 – Lukas Cenovsky Jun 30 '10 at 10:35
  • 12
    Can't you just `return (str(o),)` instead? `[o]` is a list with only 1 element, why bother looping over it? – mpen Aug 14 '11 at 23:09
  • 2
    @Mark: `return (str(o),)` would return tuple of length 1, while code in the answer returns generator of length 1. See the [iterencode() docs](http://docs.python.org/2/library/json.html#json.JSONEncoder.iterencode) – Abgan Jul 18 '13 at 10:08
  • 38
    This implementation doesn't work anymore. Elias Zamaria's one is the one working on the same style. – piro Mar 12 '14 at 17:17
  • This worked well for me for a bit, but I had a few issues with the `super` call due to the class getting reloaded (within a webserver and using a lot of pickle-based caching). Replacing it with `super(self.__class__, self)._iterencode(o, markers)`, seemed to fix it. – FredL May 21 '14 at 15:42
  • @Abgan, still `iter((o, ))` is probably faster. timeit says it's 3 times faster in Python 3.5. – Cristian Ciupitu Jul 17 '17 at 05:57
  • My **encode and decode** [answer is here](https://stackoverflow.com/questions/43678946/python-3-x-cannot-serialize-decimal-to-json/53752866#53752866) – warrens Dec 13 '18 at 13:35
  • For the current version of `json`, the `_iterencode` function should be replaced with `default` without the `markers` keyword argument. – Dewald Abrie Sep 01 '20 at 07:16
  • 4
    Its getting me this error: TypeError: at 0x7fd42908da20> is not JSON serializable – Anil Koppula Jan 05 '21 at 14:01
  • This is solution not working for `{'merchant': {'store': [Decimal('1')]}}` use `return str(o)` instead of `return (str(o) for o in [o])` – Manivannan Murugavel Apr 21 '23 at 08:07
  • Have you tried your code? It results in the value in JSON being a string and not a float, as TS wanted. I have just tried it and I get `"batchId": "31705"` instead of `"batchId": 31705`. – Azimuth May 10 '23 at 14:56
66

The native Django option is missing so I'll add it for the next guy/gall that looks for it.

Starting on Django 1.7.x there is a built-in DjangoJSONEncoder that you can get it from django.core.serializers.json.

import json
from django.core.serializers.json import DjangoJSONEncoder
from django.forms.models import model_to_dict

model_instance = YourModel.object.first()
model_dict = model_to_dict(model_instance)

json.dumps(model_dict, cls=DjangoJSONEncoder)

Presto!

Javier Buzzi
  • 6,296
  • 36
  • 50
  • 1
    Although this is great to know, the OP didn't ask about Django? – std''OrgnlDave Jul 25 '18 at 14:11
  • 19
    @std''OrgnlDave you are 100% correct. I forgot how i got here, but i googled this question with "django" attached to the search term and this came up, after a little more googling, i found the answer and added it here for the next person like me, that stumbles across it – Javier Buzzi Jul 25 '18 at 15:55
57

In my Flask app, Which uses python 2.7.11, flask alchemy(with 'db.decimal' types), and Flask Marshmallow ( for 'instant' serializer and deserializer), i had this error, every time i did a GET or POST. The serializer and deserializer, failed to convert Decimal types into any JSON identifiable format.

I did a "pip install simplejson", then Just by adding

import simplejson as json

the serializer and deserializer starts to purr again. I did nothing else... DEciamls are displayed as '234.00' float format.

ISONecroMAn
  • 1,460
  • 2
  • 16
  • 23
  • 1
    Oddly enough, you don't even have to import `simplejson` - just installing it does the trick. Initially mentioned by [this answer](https://stackoverflow.com/a/15224186/2738164). – bsplosion Mar 26 '19 at 14:28
  • 1
    This does not work on me, and still got that `Decimal('0.00') is not JSON serializable` after installing it via pip. This situation is when you are using both marshmallow and graphene. When a query is called on a rest api, marshmallow works expectedly for decimal fields. However when it is called with graphql it raised an `is not JSON serializable` error. – Shift 'n Tab Apr 02 '19 at 09:45
  • Perfect! This works in situations where you're using a module written by someone else whcih you can't easily modify (in my case gspread for using Google Sheets) – happyskeptic Nov 22 '19 at 01:53
39

I tried switching from simplejson to builtin json for GAE 2.7, and had issues with the decimal. If default returned str(o) there were quotes (because _iterencode calls _iterencode on the results of default), and float(o) would remove trailing 0.

If default returns an object of a class that inherits from float (or anything that calls repr without additional formatting) and has a custom __repr__ method, it seems to work like I want it to.

import json
from decimal import Decimal

class fakefloat(float):
    def __init__(self, value):
        self._value = value
    def __repr__(self):
        return str(self._value)

def defaultencode(o):
    if isinstance(o, Decimal):
        # Subclass float with custom repr?
        return fakefloat(o)
    raise TypeError(repr(o) + " is not JSON serializable")

json.dumps([10.20, "10.20", Decimal('10.20')], default=defaultencode)
'[10.2, "10.20", 10.20]'
tesdal
  • 2,409
  • 16
  • 11
  • Nice! This makes sure the decimal value ends up in the JSON as a Javascript float, without having Python first round it to the nearest float value. – konrad Apr 06 '13 at 13:36
  • 3
    Unfortunately this doesn't work in recent Python 3's. There is now some fast-path code that considers all float-subclasses as floats, and doesn't call repr on them altogether. – Antti Haapala -- Слава Україні Jan 15 '17 at 21:52
  • @AnttiHaapala, the example works fine on Python 3.6. – Cristian Ciupitu Jul 17 '17 at 06:52
  • @CristianCiupitu indeed, I don't seem to be able to reproduce the bad behaviour now – Antti Haapala -- Слава Україні Jul 17 '17 at 07:16
  • An issue with this answer is that it depends on the fact that the current JSON implementation uses [`float.__repr__` for floats](https://github.com/python/cpython/blob/v3.6.2/Lib/json/encoder.py#L223). On the other hand [`int.__str__` is used for integers](https://github.com/python/cpython/blob/v3.6.2/Lib/json/encoder.py#L271), so it's possible that things might change for floats. – Cristian Ciupitu Jul 17 '17 at 07:36
  • 5
    The solution stopped working since v3.5.2rc1, see https://github.com/python/cpython/commit/e0805cf10ea84b44a13ad5649267edba7cb83ee9. There is `float.__repr__` hardcoded (that looses precision), and `fakefloat.__repr__` is not called at all. The solution above does work properly for python3 up to 3.5.1, if fakefloat has additional method `def __float__(self): return self`. – myroslav Oct 17 '17 at 17:40
29

For Django users:

Recently came across TypeError: Decimal('2337.00') is not JSON serializable while JSON encoding i.e. json.dumps(data)

Solution:

# converts Decimal, Datetime, UUIDs to str for Encoding
from django.core.serializers.json import DjangoJSONEncoder  

json.dumps(response.data, cls=DjangoJSONEncoder)

But, now the Decimal value will be a string, now we can explicitly set the decimal/float value parser when decoding data, using parse_float option in json.loads:

import decimal 

data = json.loads(data, parse_float=decimal.Decimal) # default is float(num_str)
Nabeel Ahmed
  • 18,328
  • 4
  • 58
  • 63
15

For those who don't want to use a third-party library... An issue with Elias Zamaria's answer is that it converts to float, which can run into problems. For example:

>>> json.dumps({'x': Decimal('0.0000001')}, cls=DecimalEncoder)
'{"x": 1e-07}'
>>> json.dumps({'x': Decimal('100000000000.01734')}, cls=DecimalEncoder)
'{"x": 100000000000.01733}'

The JSONEncoder.encode() method lets you return the literal json content, unlike JSONEncoder.default(), which has you return a json compatible type (like float) that then gets encoded in the normal way. The problem with encode() is that it (normally) only works at the top level. But it's still usable, with a little extra work (python 3.x):

import json
from collections.abc import Mapping, Iterable
from decimal import Decimal

class DecimalEncoder(json.JSONEncoder):
    def encode(self, obj):
        if isinstance(obj, Mapping):
            return '{' + ', '.join(f'{self.encode(k)}: {self.encode(v)}' for (k, v) in obj.items()) + '}'
        if isinstance(obj, Iterable) and (not isinstance(obj, str)):
            return '[' + ', '.join(map(self.encode, obj)) + ']'
        if isinstance(obj, Decimal):
            return f'{obj.normalize():f}'  # using normalize() gets rid of trailing 0s, using ':f' prevents scientific notation
        return super().encode(obj)

Which gives you:

>>> json.dumps({'x': Decimal('0.0000001')}, cls=DecimalEncoder)
'{"x": 0.0000001}'
>>> json.dumps({'x': Decimal('100000000000.01734')}, cls=DecimalEncoder)
'{"x": 100000000000.01734}'
ecp
  • 323
  • 3
  • 7
  • Thanks for this. It was exactly what I was looking for. It outputs as a number but without the loss of precision due to float conversion. I ran into a little issue because I am using `json.dump` instead of `json.dumps` and you have to override `iterencode` instead of `encode` as explain [here](https://stackoverflow.com/questions/52939176/json-encoder-different-results-for-json-dump-and-json-dumps). – JimmyJames Nov 22 '20 at 16:48
14

3.9 can not be exactly represented in IEEE floats, it will always come as 3.8999999999999999, e.g. try print repr(3.9), you can read more about it here:

http://en.wikipedia.org/wiki/Floating_point
http://docs.sun.com/source/806-3568/ncg_goldberg.html

So if you don't want float, only option you have to send it as string, and to allow automatic conversion of decimal objects to JSON, do something like this:

import decimal
from django.utils import simplejson

def json_encode_decimal(obj):
    if isinstance(obj, decimal.Decimal):
        return str(obj)
    raise TypeError(repr(obj) + " is not JSON serializable")

d = decimal.Decimal('3.5')
print simplejson.dumps([d], default=json_encode_decimal)
Anurag Uniyal
  • 85,954
  • 40
  • 175
  • 219
14

My $.02!

I extend a bunch of the JSON encoder since I am serializing tons of data for my web server. Here's some nice code. Note that it's easily extendable to pretty much any data format you feel like and will reproduce 3.9 as "thing": 3.9

JSONEncoder_olddefault = json.JSONEncoder.default
def JSONEncoder_newdefault(self, o):
    if isinstance(o, UUID): return str(o)
    if isinstance(o, datetime): return str(o)
    if isinstance(o, time.struct_time): return datetime.fromtimestamp(time.mktime(o))
    if isinstance(o, decimal.Decimal): return str(o)
    return JSONEncoder_olddefault(self, o)
json.JSONEncoder.default = JSONEncoder_newdefault

Makes my life so much easier...

std''OrgnlDave
  • 3,912
  • 1
  • 25
  • 34
  • 5
    This is incorrect: it will reproduce 3.9 as `"thing": "3.9"`. – Glyph Dec 13 '17 at 22:24
  • @Glyph via JSON standards (of which there are a few...), an unquoted number is a double-precision floating-point, not a decimal number. Quoting it is the only way to guarantee compatibility. – std''OrgnlDave Jan 19 '18 at 19:02
  • 3
    do you have a citation for this? Every spec I’ve read implies that it’s implementation-dependent. – Glyph Jan 20 '18 at 23:29
11

From the JSON Standard Document, as linked in json.org:

JSON is agnostic about the semantics of numbers. In any programming language, there can be a variety of number types of various capacities and complements, fixed or floating, binary or decimal. That can make interchange between different programming languages difficult. JSON instead offers only the representation of numbers that humans use: a sequence of digits. All programming languages know how to make sense of digit sequences even if they disagree on internal representations. That is enough to allow interchange.

So it's actually accurate to represent Decimals as numbers (rather than strings) in JSON. Bellow lies a possible solution to the problem.

Define a custom JSON encoder:

import json


class CustomJsonEncoder(json.JSONEncoder):

    def default(self, obj):
        if isinstance(obj, Decimal):
            return float(obj)
        return super(CustomJsonEncoder, self).default(obj)

Then use it when serializing your data:

json.dumps(data, cls=CustomJsonEncoder)

As noted from comments on the other answers, older versions of python might mess up the representation when converting to float, but that's not the case anymore.

To get the decimal back in Python:

Decimal(str(value))

This solution is hinted in Python 3.0 documentation on decimals:

To create a Decimal from a float, first convert it to a string.

Hugo Mota
  • 11,200
  • 9
  • 42
  • 60
  • 5
    This isn't "fixed" in Python 3. Converting to a `float` *necessarily* makes you lose the decimal representation, and *will* lead to discrepancies. If `Decimal` is important to use, I think it is better to use strings. – juanpa.arrivillaga Jan 01 '19 at 19:58
  • I believe it's safe to do this since python 3.1. The loss in precision might be harmful in arithmetic operations, but in the case of JSON encoding, you are merely producing a string display of the value, so the precision is more than enough for most use cases. Everything in JSON is a string already, so putting quotes around the value just defies the JSON spec. – Hugo Mota Jan 26 '19 at 19:51
  • With that said, I understand the concerns around converting to float. There's probably a different strategy to use with the encoder to produce the wanted display string. Still, I don't think it's worth producing a quoted value. – Hugo Mota Jan 26 '19 at 19:53
  • @HugoMota " Everything in JSON is a string already, so putting quotes around the value just defies the JSON spec." No: https://www.rfc-editor.org/rfc/rfc8259.txt -- JSON is a text-based encoding format, but that doesn't mean everything in it is to be interpreted as a string. The spec defines how to encode numbers, separately from strings. – Gunnar Þór Magnússon Aug 23 '19 at 07:41
  • @GunnarÞórMagnússon "JSON is a text-based encoding format" - that's what I meant with "everything is a string". Converting the numbers to string beforehand won't magically preserve precision since it will be a string anyway when it becomes JSON. And according to the spec, numbers don't have quotes around it. **It's the reader's responsibility to preserve precision while reading** (not a quote, just my take on it). – Hugo Mota Aug 27 '19 at 11:51
8

This is what I have, extracted from our class

class CommonJSONEncoder(json.JSONEncoder):

    """
    Common JSON Encoder
    json.dumps(myString, cls=CommonJSONEncoder)
    """

    def default(self, obj):

        if isinstance(obj, decimal.Decimal):
            return {'type{decimal}': str(obj)}

class CommonJSONDecoder(json.JSONDecoder):

    """
    Common JSON Encoder
    json.loads(myString, cls=CommonJSONEncoder)
    """

    @classmethod
    def object_hook(cls, obj):
        for key in obj:
            if isinstance(key, six.string_types):
                if 'type{decimal}' == key:
                    try:
                        return decimal.Decimal(obj[key])
                    except:
                        pass

    def __init__(self, **kwargs):
        kwargs['object_hook'] = self.object_hook
        super(CommonJSONDecoder, self).__init__(**kwargs)

Which passes unittest:

def test_encode_and_decode_decimal(self):
    obj = Decimal('1.11')
    result = json.dumps(obj, cls=CommonJSONEncoder)
    self.assertTrue('type{decimal}' in result)
    new_obj = json.loads(result, cls=CommonJSONDecoder)
    self.assertEqual(new_obj, obj)

    obj = {'test': Decimal('1.11')}
    result = json.dumps(obj, cls=CommonJSONEncoder)
    self.assertTrue('type{decimal}' in result)
    new_obj = json.loads(result, cls=CommonJSONDecoder)
    self.assertEqual(new_obj, obj)

    obj = {'test': {'abc': Decimal('1.11')}}
    result = json.dumps(obj, cls=CommonJSONEncoder)
    self.assertTrue('type{decimal}' in result)
    new_obj = json.loads(result, cls=CommonJSONDecoder)
    self.assertEqual(new_obj, obj)
James Lin
  • 25,028
  • 36
  • 133
  • 233
4

You can create a custom JSON encoder as per your requirement.

import json
from datetime import datetime, date
from time import time, struct_time, mktime
import decimal

class CustomJSONEncoder(json.JSONEncoder):
    def default(self, o):
        if isinstance(o, datetime):
            return str(o)
        if isinstance(o, date):
            return str(o)
        if isinstance(o, decimal.Decimal):
            return float(o)
        if isinstance(o, struct_time):
            return datetime.fromtimestamp(mktime(o))
        # Any other serializer if needed
        return super(CustomJSONEncoder, self).default(o)

The Decoder can be called like this,

import json
from decimal import Decimal
json.dumps({'x': Decimal('3.9')}, cls=CustomJSONEncoder)

and the output will be:

>>'{"x": 3.9}'
sparrow
  • 222
  • 2
  • 5
2

Based on stdOrgnlDave answer I have defined this wrapper that it can be called with optional kinds so the encoder will work only for certain kinds inside your projects. I believe the work should be done inside your code and not to use this "default" encoder since "it is better explicit than implicit", but I understand using this will save some of your time. :-)

import time
import json
import decimal
from uuid import UUID
from datetime import datetime

def JSONEncoder_newdefault(kind=['uuid', 'datetime', 'time', 'decimal']):
    '''
    JSON Encoder newdfeault is a wrapper capable of encoding several kinds
    Use it anywhere on your code to make the full system to work with this defaults:
        JSONEncoder_newdefault()  # for everything
        JSONEncoder_newdefault(['decimal'])  # only for Decimal
    '''
    JSONEncoder_olddefault = json.JSONEncoder.default

    def JSONEncoder_wrapped(self, o):
        '''
        json.JSONEncoder.default = JSONEncoder_newdefault
        '''
        if ('uuid' in kind) and isinstance(o, uuid.UUID):
            return str(o)
        if ('datetime' in kind) and isinstance(o, datetime):
            return str(o)
        if ('time' in kind) and isinstance(o, time.struct_time):
            return datetime.fromtimestamp(time.mktime(o))
        if ('decimal' in kind) and isinstance(o, decimal.Decimal):
            return str(o)
        return JSONEncoder_olddefault(self, o)
    json.JSONEncoder.default = JSONEncoder_wrapped

# Example
if __name__ == '__main__':
    JSONEncoder_newdefault()
Juanmi Taboada
  • 539
  • 10
  • 25
2

My solution:---

import json
from decimal import *

num = Decimal('8439.67')


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


json_str = json.dumps({'number': num}, cls=DecimalEncoder)
print(json_str)
print(type(json_str))

'''
Output:-
{"number": "8439.67"}
<class 'str'>
'''
  • This is a wrong solution since the value in the JSON is a string, while the TS is expecting the value to be a float. – Azimuth May 10 '23 at 14:44
1

If someone is still looking for the answer, it is most probably you have a 'NaN' in your data that you are trying to encode. Because NaN is considered as float by Python.

  • This does not really answer the question. If you have a different question, you can ask it by clicking [Ask Question](https://stackoverflow.com/questions/ask). To get notified when this question gets new answers, you can [follow this question](https://meta.stackexchange.com/q/345661). Once you have enough [reputation](https://stackoverflow.com/help/whats-reputation), you can also [add a bounty](https://stackoverflow.com/help/privileges/set-bounties) to draw more attention to this question. - [From Review](/review/late-answers/30216739) – Smit Gajera Oct 30 '21 at 11:11
1

My 2 cents for easy solution, if you're sure Decimal is the only bad guy on your json dumps method:

print(json.loads(json.dumps({
    'a': Decimal(1230),
    'b': Decimal(11111111123.22),
}, default=lambda x: eval(str(x)))))

>>> {'a': 1230, 'b': 11111111123.22}

The "smart" thing here is using default to convert Decimal to int or float, automatically, taking advantage of eval function: default=lambda x: eval(str(x))

But always be careful using eval on your code as it can lead to security issues ;)

Marquinho Peli
  • 4,795
  • 4
  • 24
  • 22
0

If you want to pass a dictionary containing decimals to the requests library (using the json keyword argument), you simply need to install simplejson:

$ pip3 install simplejson    
$ python3
>>> import requests
>>> from decimal import Decimal
>>> # This won't error out:
>>> requests.post('https://www.google.com', json={'foo': Decimal('1.23')})

The reason of the problem is that requests uses simplejson only if it is present, and falls back to the built-in json if it is not installed.

Max Malysh
  • 29,384
  • 19
  • 111
  • 115
0

For anybody that wants a quick solution here is how I removed Decimal from my queries in Django

total_development_cost_var = process_assumption_objects.values('total_development_cost').aggregate(sum_dev = Sum('total_development_cost', output_field=FloatField()))
total_development_cost_var = list(total_development_cost_var.values())
  • Step 1: use , output_field=FloatField() in you r query
  • Step 2: use list eg list(total_development_cost_var.values())

Hope it helps

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Gunnit
  • 1,064
  • 5
  • 22
  • 45
0

Decimal is not suitable to be converted through:

  • float due to precision problems
  • str due to openapi restrictions

We still need direct decimal to a number json serialisation.

Here is our extension of @tesdal 's fakefloat solution (closed in v3.5.2rc1). It uses fakestr + monkeypatching to avoid quotation and "floatation" of decimals.

import json.encoder
from decimal import Decimal


def encode_fakestr(func):
    def wrap(s):
        if isinstance(s, fakestr):
            return repr(s)
        return func(s)
    return wrap


json.encoder.encode_basestring = encode_fakestr(json.encoder.encode_basestring)
json.encoder.encode_basestring_ascii = encode_fakestr(json.encoder.encode_basestring_ascii)


class fakestr(str):
    def __init__(self, value):
        self._value = value
    def __repr__(self):
        return str(self._value)


class DecimalJsonEncoder(json.encoder.JSONEncoder):
    def default(self, o):
        if isinstance(o, Decimal):
            return fakestr(o)
        return super().default(o)


json.dumps([Decimal('1.1')], cls=DecimalJsonEncoder)

[1.1]

I don't understand why python developers force us using floats in places where it is not suitable.

Alexander Klimenko
  • 2,252
  • 1
  • 18
  • 20
0

I will share what worked for me with flask 2.1.0 When I was creating the dictionary which had to be used from jsonify I used rounding:

json_dict['price'] = round(self.price, ndigits=2) if self.price else 0

So this way I could return D.DD number or 0 without using some global configuration. And this is nice because some Decimals has to be bigger, like latitude and longitude coordinates.

return jsonify(json_dict)
makkasi
  • 6,328
  • 4
  • 45
  • 60
0

Seems like there are enough answers in here but I got here needing a way to convert a list of numpy numbers to rounded decimals ready for export with simplejson

def encode_floats(nums, decimals=3):
    """
    Convert numpy numbers to ones ready for dumping with simplejson
    useful when you don't want your json exports to be bloated with too many
    significant figures
    :param nums: list of numbers
    :param decimals: number of decimals you want to output
    """
    def convert_to_rounded_dec(num):
        # convert to decimal
        num_dec = Decimal(np.float64(num))
        # create decimal number for quantizing
        round_dec = Decimal("".join(['0.'] + ['0'] * (decimals - 1) + ['1']))
        # actually round the number here
        num_round = num_dec.quantize(round_dec, rounding='ROUND_HALF_UP')
        return num_round
    # apply the function to the list of numbers
    func = (np.vectorize(convert_to_rounded_dec))
    # remove the numpy data type by converting to a list
    return func(nums).tolist()
jlansey
  • 3,996
  • 2
  • 17
  • 15
0

For whathever reason my requirement was to omit unneccessary 0's, so e.g. 123.0 should become 123. Also my requirement was to have the json output as number and not as string.

So thanks to Elias Zamarias solution I came up with the following:

class DecimalEncoder(json.JSONEncoder):
    def default(self, o):
        if isinstance(o, decimal.Decimal):
            if float(o) == int(o):
                return int(o)
            else:
                return float(o)
        return super(DecimalEncoder, self).default(o)
Thomas Sparber
  • 2,827
  • 2
  • 18
  • 34
-1

This question is old, but there seems to be a better and much simpler solution in Python3 for most use-cases:

number = Decimal(0.55)
converted_number = float(number) # Returns: 0.55 (as type float)

You can just convert Decimal to float.

neisor
  • 384
  • 4
  • 15