This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: Cannot override JSON encoding of basic type subclasses
Type: behavior Stage: needs patch
Components: Library (Lib) Versions: Python 3.8
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: serhiy.storchaka Nosy List: Tom.Brown, amirouche, barry, daniel.urban, eric.araujo, ezio.melotti, nikow, rhettinger, serhiy.storchaka, sfreilich, wiz21
Priority: normal Keywords:

Created on 2011-07-29 18:28 by barry, last changed 2022-04-11 14:57 by admin.

Messages (9)
msg141406 - (view) Author: Barry A. Warsaw (barry) * (Python committer) Date: 2011-07-29 18:28
I found this out while experimenting with enum types that inherit from int.  The json library provides for extending the encoder to handle non-basic types; in those cases, your class's .default() method is called.  However, it is impossible to override how basic types are encoded.

Worse, if you subclass int, you cannot override how instances of your subclass get encoded, because _iterencode() does isinstance() checks.  So enum values which subclass int cannot be properly encoded.

I think the isinstance checks should be changed to explicit type equality tests, e.g. `type(o) is int`.
msg142458 - (view) Author: Niko Wilbert (nikow) Date: 2011-08-19 14:23
This issue is also a real pain when using namedtuple. In many situations a namedtuple should be serialized as a dict, but there is no reasonable way to get this behavior.

Earlier solutions (e.g., see http://stackoverflow.com/questions/5906831/serializing-a-python-namedtuple-to-json) are now broken in Python 2.7.
msg231983 - (view) Author: Stefan Champailler (wiz21) Date: 2014-12-02 10:03
I'm adding a scenario for this problem, a real life one, so it gives a bit more substance.

I use SQLALchemy. I do queries with it which returns KeyedTuples (an SQLALchemy type). KeyedTuples inherits from tuple. KeyedTuples are, in principle, like NamedTuple. I want to transmit the result of my queries over json. Since KeyedTuple inherit from tuple, json can't serialize them. Of course one might say that that problem is outside the scope of json. But if so, it means we have to first convert KeyedTuples to dict and then pass the dict to json. That's alot of copies to do... This problem is rather big because it affects my whole API (close to a hundred of functions)...

I've looked into the code and as noted above, one could replace 'isinstance(x,tuple)' with 'type(x) == tuple'. But without knowledge of how many people use the flexibility introduced by isinstance, it's dangerous. We could also change the nature of default and say that default is called before any type checking in json (so default is not a 'default' anymore). We could also duplicate the default functionnality (so a function called before any type checks and then a default called if all type checks fail). But for these two last cases, I guess the difficulty is : how do we know the pre-type 'default' was applied correctly ?

Patching is not easy because, at least in my case, the C code path is taken => an easy patch (i.e. full python) would force me out of the C path which may be bad for performance (I didn't measure the difference between the 2 paths).

I see this bug is old and not much commented, should we conclude that nobody cares ? That'd a good news since it'd mean a patch wouldn't hurt many people :-)
msg231985 - (view) Author: Stefan Champailler (wiz21) Date: 2014-12-02 10:34
Reading bugs a bit, I see this is quite related to :

http://bugs.python.org/issue14886

stF
msg329956 - (view) Author: Tom Brown (Tom.Brown) Date: 2018-11-15 18:15
I found this work-around useful https://stackoverflow.com/a/32782927
msg329958 - (view) Author: Éric Araujo (eric.araujo) * (Python committer) Date: 2018-11-15 18:24
A modern solution for this is to define a singledispatch function (with implementations for your custom types) and pass it as the `default` parameter to the dump functions.

(That won’t avoid the custom → dict copy, but that’s how the module works)
msg329960 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2018-11-15 18:44
I'm working on a large patch which will include also the fix for this issue. This is a significant change and it can affect behavior and performance of existing code, so it can be made only in new version.
msg389995 - (view) Author: Samuel Freilich (sfreilich) * Date: 2021-04-01 15:26
> A modern solution for this is to define a singledispatch function (with implementations for your custom types) and pass it as the `default` parameter to the dump functions.

Does that work? I thought the default function only got called for non-serializable types.

I'm running into the same issue with some code that would like to do round-trip serialization of a datastructure (which doesn't need 100% generality of supported values but would like to deal with the existing structure of types). Dealing with set is easy, for example:

def default(obj):  # pass to default parameter of json.dumps
  if isinstance(obj, frozenset):
    return {'_class': 'set', 'items': list(obj)}
  ...

def object_hook(obj):  # pass to object_hook parameter of json.loads
  if obj.get('_class') == 'set':
    return set(decoded_dict['items'])
  ...

But you can't do the equivalent thing for tuple, even if you override encode/iterencode. It seems like the JSON module is making performance versus generality tradeoffs, it doesn't make a fresh call to encode/iterencode for every dict key/value for example. Which is fine, but it would be nice if there was a mode that allowed getting custom behavior for every type, taking the performance cost.
msg389996 - (view) Author: Samuel Freilich (sfreilich) * Date: 2021-04-01 15:31
A fully general solution for this might require a separate way to override the behavior for serializing dict keys (since those have to be serialized as strings).
History
Date User Action Args
2022-04-11 14:57:20adminsetgithub: 56866
2021-04-01 15:31:03sfreilichsetmessages: + msg389996
2021-04-01 15:26:19sfreilichsetnosy: + sfreilich
messages: + msg389995
2018-11-15 18:44:52serhiy.storchakasetversions: + Python 3.8, - Python 2.7, Python 3.4
nosy: + serhiy.storchaka

messages: + msg329960

assignee: serhiy.storchaka
2018-11-15 18:24:28eric.araujosetmessages: + msg329958
2018-11-15 18:15:14Tom.Brownsetnosy: + Tom.Brown
messages: + msg329956
2015-02-18 15:11:02serhiy.storchakalinkissue23473 superseder
2014-12-02 10:34:12wiz21setmessages: + msg231985
2014-12-02 10:03:19wiz21setnosy: + wiz21

messages: + msg231983
versions: + Python 3.4
2011-10-27 09:47:34floxsettype: behavior
stage: needs patch
2011-10-27 07:17:07ezio.melottisetnosy: + ezio.melotti
2011-10-26 16:46:24amirouchesetnosy: + amirouche
2011-08-19 14:23:36nikowsetnosy: + nikow

messages: + msg142458
versions: + Python 2.7, - Python 3.3
2011-08-08 15:49:52eric.araujosetnosy: + rhettinger, eric.araujo
2011-07-30 13:06:23daniel.urbansetnosy: + daniel.urban
2011-07-29 18:28:07barrycreate