How can I serialize a MongoDB ObjectId with Marshmallow?

前端 未结 4 1464
你的背包
你的背包 2021-02-14 07:48

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:



        
4条回答
  •  北海茫月
    2021-02-14 08:02

    Similar to @dcollien above, I extended fields.Field and created my own custom field with helpers, similar to how Marshmallow handles field types internally:

    from marshmallow import fields, missing
    from marshmallow.exceptions import ValidationError
    from bson.objectid import ObjectId
    from bson.errors import InvalidId
    import json
    
    def oid_isval(val: Any) -> bool:
        """
        oid_isval [summary]
    
        Parameters
        ----------
        val : {Any}
            Value to be assessed if its an ObjectId
    
        Returns
        ----------
        val : bool
            True if val is an ObjectId, otherwise false
        """    
        if ObjectId.is_valid(val):
            return val
    
    def ensure_objid_type(val: Union[bytes, str, ObjectId]) -> ObjectId:
        """
        Ensures that the value being passed is return as an ObjectId and is a valid ObjectId
    
        Parameters
        ----------
        val : Union[bytes, str, ObjectId]
            The value to be ensured or converted into an ObjectId and is a valid ObjectId
    
        Returns
        ----------
        val : ObjectId
            Value of type ObjectId
            
        Raises
        ----------
        ValidationError: Exception
            If it's not an ObjectId or can't be converted into an ObjectId, raise an error.
                
        """
        try:
            # If it's already an ObjectId and it's a valid ObjectId, return it
            if isinstance(val, ObjectId) and oid_isval(val):
                logger.info(f"It's an ObjectId and it's valid! = {val}")
                return val
            
            # Otherwise, if it's a bytes object, decode it and turn it into a string
            elif isinstance(val, bytes):
                val = ObjectId(str(val.decode("utf-8")))
                logger.info(f"Decoded and converted bytes object to ObjectId! = {val}")
    
            # Otherwise, if it's a string, turn it into an ObjectId and check that it's valid 
            elif isinstance(val, str):
                val = ObjectId(val)
                logger.info(f"Converted str to ObjectId! = {val}")
            
            # Check to see if the converted value is a valid objectId
            if oid_isval(val):
                logger.info(f"It's a valid ObjectId! = {val}")
                return val
        except InvalidId as error:
            logger.error(f"Not a valid ObjectId = {val} | error = {error}")
            raise ValidationError(json.loads(json.dumps(f"{error}")))
    
    
    class ObjectIdField(fields.Field):
        """Custom field for ObjectIds."""
        # Default error messages
        default_error_messages = {
            "invalid_ObjectId": "Not a valid ObjectId."
        }
    
        def _serialize(self, value, attr, obj, **kwargs) -> Optional[ObjectId]:
            if value is None:
                return None
            return ensure_objid_type(value)
    
        def _deserialize(self, value, attr, data, **kwargs):
            if value is None:
                return missing
            if not isinstance(value, (ObjectId, str, bytes)):
                raise self.make_error("_deserialize: Not a invalid ObjectId")
            try:
                return ensure_objid_type(value)
            except UnicodeDecodeError as error:
                raise self.make_error("invalid_utf8") from error
            except (ValueError, AttributeError, TypeError) as error:
                raise ValidationError("ObjectIds must be a 12-byte input or a 24-character hex string") from error
    

提交回复
热议问题