You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
In 2.7 and other versions, the json module has incomplete support for decimals:
>>> json.loads('0.2', parse_float=Decimal)
Decimal('0.2')
>>> json.dumps(json.loads('0.2', parse_float=Decimal))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python2.7/json/__init__.py", line 231, in dumps
return _default_encoder.encode(obj)
File "/usr/lib/python2.7/json/encoder.py", line 201, in encode
chunks = self.iterencode(o, _one_shot=True)
File "/usr/lib/python2.7/json/encoder.py", line 264, in iterencode
return _iterencode(o, 0)
File "/usr/lib/python2.7/json/encoder.py", line 178, in default
raise TypeError(repr(o) + " is not JSON serializable")
TypeError: Decimal('0.2') is not JSON serializable
simplejson encodes decimals out of the box, but json can’t round-trip.
You can extend this to support complex numbers, fractions, date and time, and many other custom types. Have specialized options for this would be cumbersome.
Judging by the discussion that Éric points to, and by the various stackoverflow questions on the topic ([1], [2]), this is a common enough need that I think it would make sense to have some support for it in the std. lib.
There's a sense in which Decimal is the 'right' type for json, and we shouldn't make it harder for people to do the right thing with respect to (e.g.) financial data in databases.
Decimal numbers should simply be serializable by default. It doesn't make sense to add a specialized option.
Reacted by mjog, joooeey, Nice Zombies, Edgar Ramírez Mondragón, Chris Herrick, Daniel Skýpala, Bálint János Gergely, Cristhian Paredes and AndersMunch
was has to be located AFTER lists and dicts are handled in the JSON encoder, otherwise the unittest "test_highly_nested_objects_encoding()" from test_recursion.py fails with a nasty, unrecoverable Python exception.
My guess is that this is due additional stack allocation when the stack space is almost used up by the deeply nested recursion code.
Patch looks good and contains tests for the C and Python code.
Documentation is missing (a note to tell that json.dump converts decimal.Decimal instances to JSON numbers, a versionchanged directive, maybe a link to the doc that explains parse_float=decimal.Decimal).
The patch isn't really ok, IMO. It forcibly imports the decimal module and then looks up the type there. The decimal module is a rather large one and it shouldn't get imported if it doesn't get used.
I think it would be better to rely on the __float__ special method, which would also automatically accept other numberish types such as Fraction.
skrah commented on Mar 24, 2014
skrahmannequin
Mannequin
More actions
I think we should really apply bpo-19232. At least that would take care
of the import issue.
I'm EuroPython 2014 in Berlin. Ralph has approached me and asked me about progress on the progress of this patch. I'm reluctant to implement a special case for decimals for two reasons:
JSON just support floats and decimals are IMHO incompatible with floats. The conversion of decial to JSON floats is a loosely operation.
Rather than having a special case I would rather go with a general implementation that uses an ABC to JSON dump some float-like objects.
Sadly the C implementation will have to call Python to handle sNaN and NaN payloads properly:
defencode_decimal(decimal: Decimal) ->str:
ifnotdecimal.is_finite():
ifdecimal.is_snan():
msg: str=f"{decimal!r} is not JSON serializable"raiseValueError(msg)
ifnotallow_nan_and_infinity:
msg=f"{decimal!r} is not allowed"raiseValueError(msg)
ifdecimal.is_qnan():
return"NaN"returndecimal_str(decimal)
I wonder if adding something similar to what simplejson uses
(i.e. explicitly specifying in json.dump(s) how to
serialize decimal.Decimal) could be acceptable.
+1 for this approach. For financial applications, we need the recommended solution to be simple.
Indeed, the solution should be simple. But the simplest solution is not the one quoted above but one mentioned earlier:
Decimal numbers should simply be serializable by default. It doesn't make sense to add a specialized option.
There is no need to explicitly specify how to serialize decimal.Decimal because there is only one obvious way to do it (str(decimal) plus some handling for a few corner cases which should be obvious too). Hence, the only required API change would be to add decimal.Decimal to the list of types that json.JSONEncoder accepts. Of course, someone would have to write that implementation.
Sadly the C implementation will have to call Python to handle sNaN and NaN payloads properly:
defencode_decimal(decimal: Decimal) ->str:
ifnotdecimal.is_finite():
ifdecimal.is_snan():
msg: str=f"{decimal!r} is not JSON serializable"raiseValueError(msg)
ifnotallow_nan_and_infinity:
msg=f"{decimal!r} is not allowed"raiseValueError(msg)
ifdecimal.is_qnan():
return"NaN"returndecimal_str(decimal)
I notice that this code appears equivalent to the floatstr function defined inside json.encoder.JSONEncoder.iterencode if you pass _repr=decimal.Decimal.__str__. The only difference is a less clear error for signalling NaNs (InvalidOperation: [<class 'decimal.InvalidOperation'>]).
simplejson encodes decimals out of the box, but json can’t round-trip.
simplejson does not properly support non-numeric decimals like NaN, sNaN, Infinity or -Infinity. These are not valid JSON and should raise an error, especially if allow_nan == False. But simplejson.dumps(Decimal('nan'), allow_nan=False) happily succeeds.
So people wanting to properly write IEEE 754 decimal numbers to JSON (as JSON number), are out of luck with Python. There is no working solution in the standard library, no workaround recipe (since Python 3.5) and no external library that can do it correctly. EDIT: As @nineteendo points out, the jsonyx package treats Decimal correctly. But it's not available via conda.
@nineteendo I just tried it. Appears to work flawlessly (No issues with a 76-digit number, NaN, -Infinity or -0). It also doesn't do funny things with non-string keys like json in the standard library.
Activity
merwok commentedon Nov 22, 2012
In 2.7 and other versions, the json module has incomplete support for decimals:
simplejson encodes decimals out of the box, but json can’t round-trip.
merwok commentedon Nov 22, 2012
See lengthy discussion that lead to inclusion in simplejson here: http://code.google.com/p/simplejson/issues/detail?id=34
serhiy-storchaka commentedon Nov 23, 2012
The json module already has too many options. No need for yet one such specialized.
You can extend this to support complex numbers, fractions, date and time, and many other custom types. Have specialized options for this would be cumbersome.
mdickinson commentedon Nov 23, 2012
Judging by the discussion that Éric points to, and by the various stackoverflow questions on the topic ([1], [2]), this is a common enough need that I think it would make sense to have some support for it in the std. lib.
There's a sense in which Decimal is the 'right' type for json, and we shouldn't make it harder for people to do the right thing with respect to (e.g.) financial data in databases.
[1] http://stackoverflow.com/questions/4019856/decimal-to-json
[2] http://stackoverflow.com/questions/1960516/python-json-serialize-a-decimal-object
merwok commentedon Nov 26, 2012
Thanks for the workaround Serhiy, I stole that and ran with it :)
For 3.4 I still think something built-in would be best.
pitrou commentedon Jan 13, 2013
Decimal numbers should simply be serializable by default. It doesn't make sense to add a specialized option.
ralhei commentedon Jul 7, 2013
This patch was implemented on Europython 2013 sprint. It's my first addition to Python core ever so please bear with me if it's not perfect.
Decimal support is implemented both in the C and Python JSON code.
There is one peculiarity to mention about the Decimal addition in function _json.c:encoder_listencode_obj() of my patch:
The addition of
was has to be located AFTER lists and dicts are handled in the JSON encoder, otherwise the unittest "test_highly_nested_objects_encoding()" from test_recursion.py fails with a nasty, unrecoverable Python exception.
My guess is that this is due additional stack allocation when the stack space is almost used up by the deeply nested recursion code.
merwok commentedon Mar 13, 2014
Patch looks good and contains tests for the C and Python code.
Documentation is missing (a note to tell that json.dump converts decimal.Decimal instances to JSON numbers, a versionchanged directive, maybe a link to the doc that explains parse_float=decimal.Decimal).
pitrou commentedon Mar 24, 2014
The patch isn't really ok, IMO. It forcibly imports the decimal module and then looks up the type there. The decimal module is a rather large one and it shouldn't get imported if it doesn't get used.
I think it would be better to rely on the __float__ special method, which would also automatically accept other numberish types such as Fraction.
skrah commentedon Mar 24, 2014
I think we should really apply bpo-19232. At least that would take care
of the import issue.
tiran commentedon Jul 26, 2014
I'm EuroPython 2014 in Berlin. Ralph has approached me and asked me about progress on the progress of this patch. I'm reluctant to implement a special case for decimals for two reasons:
JSON just support floats and decimals are IMHO incompatible with floats. The conversion of decial to JSON floats is a loosely operation.
Rather than having a special case I would rather go with a general implementation that uses an ABC to JSON dump some float-like objects.
5 remaining items
nineteendo commentedon Aug 22, 2024
Sadly the C implementation will have to call Python to handle sNaN and NaN payloads properly:
joooeey commentedon Aug 24, 2024
Indeed, the solution should be simple. But the simplest solution is not the one quoted above but one mentioned earlier:
There is no need to explicitly specify how to serialize
decimal.Decimalbecause there is only one obvious way to do it (str(decimal)plus some handling for a few corner cases which should be obvious too). Hence, the only required API change would be to adddecimal.Decimalto the list of types thatjson.JSONEncoderaccepts. Of course, someone would have to write that implementation.joooeey commentedon Aug 24, 2024
I notice that this code appears equivalent to the
floatstrfunction defined insidejson.encoder.JSONEncoder.iterencodeif you pass_repr=decimal.Decimal.__str__. The only difference is a less clear error for signalling NaNs (InvalidOperation: [<class 'decimal.InvalidOperation'>]).joooeey commentedon Aug 24, 2024
simplejson does not properly support non-numeric decimals like NaN, sNaN, Infinity or -Infinity. These are not valid JSON and should raise an error, especially if
allow_nan == False. Butsimplejson.dumps(Decimal('nan'), allow_nan=False)happily succeeds.So people wanting to properly write IEEE 754 decimal numbers to JSON (as JSON number), are out of luck with Python. There is no working solution in the standard library, no workaround recipe (since Python 3.5)
and no external library that can do it correctly.EDIT: As @nineteendo points out, thejsonyxpackage treatsDecimalcorrectly. But it's not available viaconda.nineteendo commentedon Aug 26, 2024
Did you try https://pypi.org/project/jsonyx? It's fully JSON compliant under default settings.
joooeey commentedon Aug 27, 2024
@nineteendo I just tried it. Appears to work flawlessly (No issues with a 76-digit number,
NaN,-Infinityor-0). It also doesn't do funny things with non-string keys likejsonin the standard library.nineteendo commentedon Aug 27, 2024
Glad you like it. It also raises errors for
1e400when not using Decimal instead of silently converting this to infinityCan you create an issue? Explaining how I need to publish it there.
joooeey commentedon Aug 27, 2024
Will do. However, I've only looked into it but never tried it, so I can't help with experience.
nineteendo commentedon Sep 25, 2024
OK, so it's possible to implement this in C: