【问题标题】:How can I serialize a MongoDB ObjectId with Marshmallow?如何使用 Marshmallow 序列化 MongoDB ObjectId?
【发布时间】:2015-01-22 16:28:15
【问题描述】:

我正在使用 marshmallow 和 mongoengine 在 Flask 上构建 API。当我拨打电话并且 ID 应该被序列化时,我收到以下错误:

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

我看到了其他库的一些方法来覆盖 ObjectId 的处理方式。我还没有用棉花糖弄明白,有人知道怎么做吗?

我的模型是:

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

我的序列化器:

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

还有观点:

class ProcessView(Resource):
    def get(self, id):
        process = Process.objects.get_or_404(id)
        return ProcessSerializer(process).data

【问题讨论】:

    标签: python flask mongoengine marshmallow


    【解决方案1】:

    当您只是将Meta.fields 传递给模式时,Marshmallow 会尝试为每个属性选择一个字段类型。由于它不知道ObjectId 是什么,它只是将它传递给序列化的字典。当您尝试将其转储到 JSON 时,它不知道 ObjectId 是什么并引发错误。要解决这个问题,您需要告诉 Marshmallow 使用哪个字段作为 id。 BSON ObjectId 可以转换为字符串,因此请使用 String 字段。

    from marshmallow import Schema, fields
    
    class ProcessSchema(Schema):
        id = fields.String()
    
        class Meta:
            additional =  ('created_at', 'name')
    

    您还可以告诉 Marshmallow 为 ObjectId 类型使用哪个字段,这样您就不必每次都添加该字段。

    from bson import ObjectId
    from marshmallow import Schema, fields
    
    Schema.TYPE_MAPPING[ObjectId] = fields.String
    

    【讨论】:

      【解决方案2】:

      marshmallow-mongoengine 这样做:

      Marshmallow-Mongoengine 是将Mongoengine 文档与Marshmallow Schema 合并在一起。

      import marshmallow_mongoengine as ma
      
      
      class ProcessSchema(ma.ModelSchema):
          class Meta:
              model = Process
      

      它有一个ObjectId 字段,用于序列化/反序列化ObjectIds。

      【讨论】:

        【解决方案3】:

        您可以将 fields.Field 类扩展为 create your own fieldHere's how marshmallow-mongoengine(在另一个答案中提到)实现了这个:

        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)
        

        然后:

        class MySchema(Schema):
            id = ObjectId()
        

        (我发现这在不使用 MongoEngine 时很有用,只使用 pymongo)

        【讨论】:

          【解决方案4】:

          类似于上面的@dcollien,我扩展了 fields.Field 并使用助手创建了自己的自定义字段,类似于 Marshmallow 在内部处理字段类型的方式:

          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
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-01-17
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-07-06
            相关资源
            最近更新 更多