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.

Title: Make Custom Object Classes JSON Serializable
Type: enhancement Stage:
Components: Versions: Python 3.8
Status: open Resolution:
Dependencies: Superseder:
Assigned To: bob.ippolito Nosy List: andrewchap, bob.ippolito, rhettinger
Priority: normal Keywords:

Created on 2018-10-30 11:43 by andrewchap, last changed 2022-04-11 14:59 by admin.

Messages (5)
msg328897 - (view) Author: andrew c (andrewchap) Date: 2018-10-30 11:43
When creating a custom class that doesn't inherit from the accepted classes, there is no way to serialize it using json.dumps or json.dump.  

I propose that __fromjson__ and __tojson__ that when present will be used by the Python's default encoder.  This issue is widely documented on stackoverflow as a deficiency. 

You basically have a couple of options to solve this:

1. Implement a custom  json.JSONEncoder/json.JSONDecoder
2. Implement a method like to/from json
3. Monkey patch the json library
4. Use a 3rd party library

These are not very good options.  If you can serialize an object using pickle, why not have the ability to serialize objects using json?

Thank you
msg328968 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2018-10-31 05:32
> If you can serialize an object using pickle,
> why not have the ability to serialize objects using json?

One reason would be that the JSON spec was intentionally designed to handle a limited number of types so that it would have maximum interoperability between languages. Another reason is that in order to make other types round-trip between encoding and decoding, you would need to control both ends, in which case, you might as well use pickle.
msg328971 - (view) Author: Bob Ippolito (bob.ippolito) * (Python committer) Date: 2018-10-31 06:17
The trouble with having such a hook is that it would take precedence over any customization you might want or need to do to satisfy the protocol you're implementing. Other than the limited set of types that are part of the JSON specification, there's essentially no standard for encoding of anything else. This is why customization is left to the call sites for encoding and decoding, and I would recommend using the `default` and `object_pairs_hook` keyword arguments to `dumps` and `loads` respectively for that, rather than any of the options that you've enumerated.

For what it's worth, simplejson has had a `for_json` method hook for several years to encourage some consolidation, but it's opt-in and there hasn't been a lot of demand to make it the default. The inverse `from_json` type operation is not supported. If you think about it, how *could* you even implement such a thing in the general case, in a way that wouldn't have lots of surprises and performance issues?
msg329003 - (view) Author: andrew c (andrewchap) Date: 2018-10-31 17:57

I understand what you are saying, but having a built-in is way easier than encoder/decoders.  You can still have both actually.  Even if you didn't have two way, a one way would be amazingly helpful.  For large development systems, a __json__ method would make serialization super easy.

class foo(object):
     a = 1
     b = [1,2,3]
     def __json__(self): return {'a':self.a,'b':self.b}
msg329004 - (view) Author: Bob Ippolito (bob.ippolito) * (Python committer) Date: 2018-10-31 18:19
That's what the for_json method is in simplejson, it does not have widespread usage.

You can implement that when encoding:

def json_default(obj):
        return obj.__json__()
    except AttributeError:
        raise TypeError("{} can not be JSON encoded".format(type(obj)))

json.dumps(foo(), default=json_default)

This way, you can choose precisely how the output needs to work when encoding. It's not ideal for every use case, but nothing is. The point is that it doesn't get in your way, whatever you need to do can be done without any awful tricks, so long as you have control over the dumps call site.
Date User Action Args
2022-04-11 14:59:07adminsetgithub: 79292
2018-10-31 18:19:47bob.ippolitosetmessages: + msg329004
2018-10-31 17:57:02andrewchapsetmessages: + msg329003
2018-10-31 06:17:23bob.ippolitosetmessages: + msg328971
2018-10-31 05:32:01rhettingersetnosy: + rhettinger

messages: + msg328968
versions: - Python 2.7, Python 3.4, Python 3.5, Python 3.6, Python 3.7
2018-10-31 05:17:28rhettingersetassignee: bob.ippolito

nosy: + bob.ippolito
2018-10-30 11:43:02andrewchapcreate