简体   繁体   中英

TypeError: Object of type <Foo> is not JSON serializable (in Array)

Package Version
Python 3.9.7
Flask 2.0.2
Flask-UUID 0.2
pip 21.3.1
setuptools 57.4.0
Werkzeug 2.0.1

Hi there, although being familiar with other similar concepts in other languages, this leaves me quite stunned .. .. I am writing a little petstore to get familiar with python - more specific flask - and stumble from error to error. The 'normal way to learn I guess ^.^

Nevermind, the code is primarily from here ( https://programminghistorian.org/en/lessons/creating-apis-with-python-and-flask#creating-the-api ) and works fine ..

import flask
from flask import request, jsonify

app = flask.Flask(__name__)
app.config["DEBUG"] = True

# Create some test data for our catalog in the form of a list of dictionaries.
books = [
    {'id': 0,
     'title': 'A Fire Upon the Deep'},
    {'id': 1,
     'title': 'The Ones Who Walk Away From Omelas'},
    {'id': 2,
     'title': 'Dhalgren'}
]

@app.route('/', methods=['GET'])
def home():
    return '''<h1>Distant Reading Archive</h1>
<p>A prototype API for distant reading of science fiction novels.</p>'''

# A route to return all of the available entries in our catalog.
@app.route('/api/v1/resources/books/all', methods=['GET'])
def api_all():
    return jsonify(books)

app.run()

.. but .. instead of a prewritten array I'd like to paste some objects before switching to an sqlite-persisence-solution (or likewise).

As far as possible I want to raise the complexity slowly to grasp the program completely.

Hence, using a customized pet class:

import flask
from flask import request, jsonify
import uuid

app = flask.Flask(__name__)
app.config["DEBUG"] = True

class Pet:
    def __init__(self,id, name, dateOfBirth):
      self.id = id.hex
      self.name = name
      self.dateOfBirth = dateOfBirth
#    def to_json(obj):
#        return json.dumps(obj, default=lambda obj: obj.__dict__)

# Create some test data for our catalog in the form of a list of dictionaries.
pets = [ Pet(uuid.uuid4(),'Hamster', '02.12.2019' )]
pets.append( Pet(uuid.uuid4(),'Fish', '07.03.1985' ))
pets.append( Pet(uuid.uuid4(),'Dog', '26.11.2000' ))
pets.append( Pet(uuid.uuid4(),'Cat', '17.05.2021' ))

@app.route('/', methods=['GET'])
def home():
    return "<h1>Petshop Archive</h1><p>This site is a prototype API for the petshop archive.</p>"

# A route to return all of the available entries in our catalog.
@app.route('/api/v1/resources/pets/all', methods=['GET'])
def api_all():
    return jsonify(pets)

if __name__ == '__main__':
    app.run(host='0.0.0.0')

.. You see, the changes are marginal. Now, requesting the API ( http://localhost:5000/api/v1/resources/pets/all ) fails giving a "TypeError: Object of type Pet is not JSON serializable" from the line ..

return jsonify(pets)

.. fair enough. Of course I googled and found fitting solutions, such as ..

  1. How to make a class JSON serializable

.. and ..

  1. python json object array not serializable

.. for the serializable problem (generally) and added the appropriate function

def to_json(obj):
        return json.dumps(obj, default=lambda obj: obj.__dict__)

Still I missed the link between the function (serializing the pet-class) and the array (recognizing the pet class to be serializabe).

My approach to write a new array to prevent any class having to be serialized by returning 'pets[pet]' ..

def api_all():
    allpets = [pet.toJson for pet in pets]
    return jsonify(allpets)

.. fails as well.

Hence the -> TypeError: Object of type Pet is not JSON serializable <- still exists.

Is there 'fancy way' to solve this issue? I can't imagine that this issue has not been raised, yet. Maybe I just looked at it from a wrong angle ;-)

Thanks everyone who made it this far :)

Finally, this helped: --> How to make a class JSON serializable

It works if I am using an encoder in combination with a loop over the array.

from json import JSONEncoder

class PetEncoder(JSONEncoder):
        def default(self, o):
            return o.__dict__  

# ... ... ...

# A route to return all of the available entries in our catalog.
@app.route('/api/v1/resources/pets/all', methods=['GET'])
def api_all():
    allpets = [PetEncoder().encode(pet) for pet in pets]
    return jsonify(allpets)

Still it feels dirty, as I need to rewrite the whole array 'unneccessarily'. I bet there has to be a better solution. Nevertheless, an array is obviously not the desired 'longterm-persisence-solution'-datatype.


Last, the whole code so far:

import flask
from flask import request, jsonify
import uuid
from json import JSONEncoder

app = flask.Flask(__name__)
app.config["DEBUG"] = True

class PetEncoder(JSONEncoder):
        def default(self, o):
            return o.__dict__   

class Pet:
    def __init__(self,id, name, dateOfBirth):
      self.id = id.hex
      self.name = name
      self.dateOfBirth = dateOfBirth

# Create some test data for our catalog in the form of a list of dictionaries.
pets = [ Pet(uuid.uuid4(),'Hamster', '02.12.2019' )]
pets.append( Pet(uuid.uuid4(),'Fish', '07.03.1985' ))
pets.append( Pet(uuid.uuid4(),'Dog', '26.11.2000' ))
pets.append( Pet(uuid.uuid4(),'Cat', '17.05.2021' ))

@app.route('/', methods=['GET'])
def home():
    return "<h1>Petshop Archive</h1><p>This site is a prototype API for the petshop archive.</p>"

# A route to return all of the available entries in our catalog.
@app.route('/api/v1/resources/pets/all', methods=['GET'])
def api_all():
    allpets = [PetEncoder().encode(pet) for pet in pets]
    return jsonify(allpets)

if __name__ == '__main__':
    app.run(host='0.0.0.0')

Happy coding :)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM