Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I serialize a MongoDB ObjectId with Marshmallow?

I'm building and API on top of Flask using marshmallow and mongoengine. When I make a call and an ID is supposed to be serialized I receive the following error:

TypeError: ObjectId('54c117322053049ba3ef31f3') is not JSON serializable

I saw some ways with other libraries to override the way the ObjectId is treated. I haven't figured it out with Marshmallow yet, does anyone know how to do that?

My model is:

class Process(db.Document):
    name = db.StringField(max_length=255, required=True, unique=True)
    created_at = db.DateTimeField(default=datetime.datetime.now, required=True)

My serializer:

class ProcessSerializer(Serializer):
    class Meta:
        fields = ("id", "created_at", "name")

And the view:

class ProcessView(Resource):
    def get(self, id):
        process = Process.objects.get_or_404(id)
        return ProcessSerializer(process).data
like image 866
Diego Silva Pires Avatar asked Jan 22 '15 16:01

Diego Silva Pires


People also ask

How an ObjectId type is composed in MongoDB?

An ObjectId in MongoDB is a 12-byte BSON type. In the 12-byte structure, the first 4 bytes of the ObjectId represent the time in seconds since the UNIX epoch. The next 3 bytes of the ObjectId represent the machine identifier. The next 2 bytes of the ObjectId represent the process ID.

What is the type of ObjectId in MongoDB?

Every document in the collection has an “_id” field that is used to uniquely identify the document in a particular collection it acts as the primary key for the documents in the collection. “_id” field can be used in any format and the default format is ObjectId of the document.

What is the use of ObjectId in MongoDB?

MongoDB uses ObjectIds as the default value of _id field of each document, which is generated while the creation of any document. The complex combination of ObjectId makes all the _id fields unique.

What is BSON ObjectId?

An ObjectId is a 12-byte BSON type hexadecimal string having the structure as shown in the example below. Example: ObjectId("6009c0eee65f6dce28fb3e50") The first 4 bytes are a timestamp value, representing the ObjectId's creation, measured in seconds since the Unix epoch. Next 5-bytes represent a random value.


2 Answers

You can extend the fields.Field class to create your own field. Here's how marshmallow-mongoengine (mentioned in another answer) implements this:

import bson
from marshmallow import ValidationError, fields, missing

class ObjectId(fields.Field):
    def _deserialize(self, value, attr, data):
        try:
            return bson.ObjectId(value)
        except Exception:
            raise ValidationError("invalid ObjectId `%s`" % value)

    def _serialize(self, value, attr, obj):
        if value is None:
            return missing
        return str(value)

and then:

class MySchema(Schema):
    id = ObjectId()

(I found this useful when not using MongoEngine, just using pymongo)

like image 96
dcollien Avatar answered Oct 09 '22 01:10

dcollien


When you just pass Meta.fields to a schema, Marshmallow tries to pick a field type for each attribute. Since it doesn't know what an ObjectId is, it just passes it on to the serialized dict. When you try to dump this to JSON, it doesn't know what an ObjectId is and raises an error. To solve this, you need to tell Marshmallow what field to use for the id. A BSON ObjectId can be converted to a string, so use a String field.

from marshmallow import Schema, fields

class ProcessSchema(Schema):
    id = fields.String()

    class Meta:
        additional =  ('created_at', 'name')

You can also tell Marshmallow what field to use for the ObjectId type so that you don't have to add the field each time.

from bson import ObjectId
from marshmallow import Schema, fields

Schema.TYPE_MAPPING[ObjectId] = fields.String
like image 44
davidism Avatar answered Oct 08 '22 23:10

davidism