62

I have a list of objects that I need to jsonify. I've looked at the flask jsonify docs, but I'm just not getting it.

My class has several inst-vars, each of which is a string: gene_id, gene_symbol, p_value. What do I need to do to make this serializable as JSON?

My naive code:

jsonify(eqtls = my_list_of_eqtls)

Results in:

TypeError: <__main__.EqtlByGene object at 0x1073ff790> is not JSON serializable

Presumably I have to tell jsonify how to serialize an EqtlByGene, but I can't find an example that shows how to serialize an instance of a class.

I've been trying to follow some of the suggestions show below to create my own JSONEncoder subclass. My code is now:

class EqtlByGene(Resource):

    def __init__(self, gene_id, gene_symbol, p_value):
        self.gene_id = gene_id
        self.gene_symbol = gene_symbol
        self.p_value = p_value

class EqtlJSONEncoder(JSONEncoder):
    def default(self, obj):
        if isinstance(obj, EqtlByGene):
            return {
                   'gene_id'     : obj.gene_id,
                   'gene_symbol' : obj.gene_symbol,
                   'p_value'     : obj.p_value
            }
        return super(EqtlJSONEncoder, self).default(obj)

class EqtlByGeneList(Resource):
    def get(self):
        eqtl1 = EqtlByGene(1, 'EGFR', 0.1)
        eqtl2 = EqtlByGene(2, 'PTEN', 0.2)
        eqtls = [eqtl1, eqtl2]
        return jsonify(eqtls_by_gene = eqtls)

api.add_resource(EqtlByGeneList, '/eqtl/eqtlsbygene')
app.json_encoder(EqtlJSONEncoder)
if __name__ == '__main__':
    app.run(debug=True)

When I try to reach it via curl, I get:

TypeError(repr(o) + " is not JSON serializable")
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Jared Nedzel
  • 779
  • 1
  • 6
  • 10
  • JSON cannot serialize class instances. However you decide to serialize your class, that serialization would not be canonical JSON and will need explicit support on the other side when deserializing your JSON. – lanzz Jan 28 '14 at 16:41

2 Answers2

94

Give your EqltByGene an extra method that returns a dictionary:

class EqltByGene(object):
    #

    def serialize(self):
        return {
            'gene_id': self.gene_id, 
            'gene_symbol': self.gene_symbol,
            'p_value': self.p_value,
        }

then use a list comprehension to turn your list of objects into a list of serializable values:

jsonify(eqtls=[e.serialize() for e in my_list_of_eqtls])

The alternative would be to write a hook function for the json.dumps() function, but since your structure is rather simple, the list comprehension and custom method approach is simpler.

You can also be really adventurous and subclass flask.json.JSONEncoder; give it a default() method that turns your EqltByGene() instances into a serializable value:

from flask.json import JSONEncoder

class MyJSONEncoder(JSONEncoder):
    def default(self, obj):
        if isinstance(obj, EqltByGene):
            return {
                'gene_id': obj.gene_id, 
                'gene_symbol': obj.gene_symbol,
                'p_value': obj.p_value,
            }
        return super(MyJSONEncoder, self).default(obj)

and assign this to the app.json_encoder attribute:

app = Flask(__name__)
app.json_encoder = MyJSONEncoder

and just pass in your list directly to jsonify():

return jsonify(my_list_of_eqtls)

You could also look at the Marshmallow project for a more full-fledged and flexible project for serializing and de-serializing objects to Python primitives that easily fit JSON and other such formats; e.g.:

from marshmallow import Schema, fields

class EqltByGeneSchema(Schema):
    gene_id = fields.Integer()
    gene_symbol = fields.String()
    p_value = fields.Float()

and then use

jsonify(eqlts=EqltByGeneSchema().dump(my_list_of_eqtls, many=True)

to produce JSON output. The same schema can be used to validate incoming JSON data and (with the appropriate extra methods), used to produce EqltByGene instances again.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • @Martijn Pieters: I tried: `code` class EqtlJSONEncoder(JSONEncoder): def default(self, obj): if isinstance(obj, EqtlByGene): return { 'gene_id' : obj.gene_id, 'gene_symbol' : obj.gene_symbol, 'p_value' : obj.p_value } return JSONEncoder.default(self, obj) .... app.json_encoder(EqtlJSONEncoder) `code` But I still get: – Jared Nedzel Jan 28 '14 at 17:35
  • @user1438352: I've updated the code and tested it locally; it works correctly for me now. – Martijn Pieters Jan 28 '14 at 17:48
  • I must be doing something wrong. I still get: TypeError: <__main__.EqtlByGene object at 0x10964e890> is not JSON serializable – Jared Nedzel Jan 28 '14 at 18:46
  • @user1438352: Then the `default` handler is not recognizing your class type; `isinstance(obj, EqltByGene)` is not true for some reason. Does a `print` statement under that `if` statement being executed? – Martijn Pieters Jan 28 '14 at 18:48
  • Martijn, no, it is not getting inside the if statement. – Jared Nedzel Jan 28 '14 at 19:02
  • @user1438352: are you using dynamic reloading of modules in your development server? If so, most likely something got out of sync here and you should shut down and start a fresh server. – Martijn Pieters Jan 28 '14 at 19:03
  • I have been shutting down the server and restarting. – Jared Nedzel Jan 28 '14 at 19:10
  • @user1438352: Ah, thanks for posting your code; you are *not* registering the custom encoder correctly. Use `app.json_encoder = EqtlJSONEncoder`, *don't* call the pre-configured encoder. – Martijn Pieters Jan 28 '14 at 19:13
  • so i have been spending all night reading these posts, and I havent found any example of how to write the other half: the JSONDecoder. So, anybody knows how? – user2617470 May 31 '20 at 18:53
  • @NO...Bugs...: that depends on how complex the structure is. I'd not use the JSON decoder for that, at any rate. Go *JSON data* -> *Python primitives* -> *instances*. Give your classes a `from_json()` classmethod that knows what keys to expect, and possibly delegate to more such classmethods on contained instances. Or use a project like marshmallow, I've added a marshmallow serialization example and a link to the documentation on deserialization for that. – Martijn Pieters Jun 02 '20 at 11:09
3

If you look at the docs for the json module, it mentions that you can subclass JSONEncoder to override its default method and add support for types there. That would be the most generic way to handle it if you're going to be serializing multiple different structures that might contain your objects.

If you want to use jsonify, it's probably easier to convert your objects to simple types ahead of time (e.g. by defining your own method on the class, as Martijn suggests).

Amber
  • 507,862
  • 82
  • 626
  • 550
  • I'm sorry, I'm a python newbie. I look at those docs and it is just gibberish to me. Could you provide an example? – Jared Nedzel Jan 28 '14 at 16:46
  • @MartijnPieters I thought `jsonify` passed `*args, **kwargs` through? Anyhow, this might be a time when it's better to not use jsonify. – Amber Jan 28 '14 at 16:48
  • @Amber: Yes, and those arguments are passed to `dict()`, and *that* is then passed to `json.dumps()`. – Martijn Pieters Jan 28 '14 at 16:49