How to serialize SqlAlchemy result to JSON?

后端 未结 27 1481
说谎
说谎 2020-11-22 09:59

Django has some good automatic serialization of ORM models returned from DB to JSON format.

How to serialize SQLAlchemy query result to JSON format?

I tried

相关标签:
27条回答
  • 2020-11-22 10:20

    I recommend using marshmallow. It allows you to create serializers to represent your model instances with support to relations and nested objects.

    Here is a truncated example from their docs. Take the ORM model, Author:

    class Author(db.Model):
        id = db.Column(db.Integer, primary_key=True)
        first = db.Column(db.String(80))
        last = db.Column(db.String(80))
    

    A marshmallow schema for that class is constructed like this:

    class AuthorSchema(Schema):
        id = fields.Int(dump_only=True)
        first = fields.Str()
        last = fields.Str()
        formatted_name = fields.Method("format_name", dump_only=True)
    
        def format_name(self, author):
            return "{}, {}".format(author.last, author.first)
    

    ...and used like this:

    author_schema = AuthorSchema()
    author_schema.dump(Author.query.first())
    

    ...would produce an output like this:

    {
            "first": "Tim",
            "formatted_name": "Peters, Tim",
            "id": 1,
            "last": "Peters"
    }
    

    Have a look at their full Flask-SQLAlchemy Example.

    A library called marshmallow-sqlalchemy specifically integrates SQLAlchemy and marshmallow. In that library, the schema for the Author model described above looks like this:

    class AuthorSchema(ModelSchema):
        class Meta:
            model = Author
    

    The integration allows the field types to be inferred from the SQLAlchemy Column types.

    marshmallow-sqlalchemy here.

    0 讨论(0)
  • 2020-11-22 10:21

    Maybe you can use a class like this

    from sqlalchemy.ext.declarative import declared_attr
    from sqlalchemy import Table
    
    
    class Custom:
        """Some custom logic here!"""
    
        __table__: Table  # def for mypy
    
        @declared_attr
        def __tablename__(cls):  # pylint: disable=no-self-argument
            return cls.__name__  # pylint: disable= no-member
    
        def to_dict(self) -> Dict[str, Any]:
            """Serializes only column data."""
            return {c.name: getattr(self, c.name) for c in self.__table__.columns}
    
    Base = declarative_base(cls=Custom)
    
    class MyOwnTable(Base):
        #COLUMNS!
    

    With that all objects have the to_dict method

    0 讨论(0)
  • 2020-11-22 10:22

    I know this is quite an older post. I took solution given by @SashaB and modified as per my need.

    I added following things to it:

    1. Field ignore list: A list of fields to be ignored while serializing
    2. Field replace list: A dictionary containing field names to be replaced by values while serializing.
    3. Removed methods and BaseQuery getting serialized

    My code is as follows:

    def alchemy_json_encoder(revisit_self = False, fields_to_expand = [], fields_to_ignore = [], fields_to_replace = {}):
       """
       Serialize SQLAlchemy result into JSon
       :param revisit_self: True / False
       :param fields_to_expand: Fields which are to be expanded for including their children and all
       :param fields_to_ignore: Fields to be ignored while encoding
       :param fields_to_replace: Field keys to be replaced by values assigned in dictionary
       :return: Json serialized SQLAlchemy object
       """
       _visited_objs = []
       class AlchemyEncoder(json.JSONEncoder):
          def default(self, obj):
            if isinstance(obj.__class__, DeclarativeMeta):
                # don't re-visit self
                if revisit_self:
                    if obj in _visited_objs:
                        return None
                    _visited_objs.append(obj)
    
                # go through each field in this SQLalchemy class
                fields = {}
                for field in [x for x in dir(obj) if not x.startswith('_') and x != 'metadata' and x not in fields_to_ignore]:
                    val = obj.__getattribute__(field)
                    # is this field method defination, or an SQLalchemy object
                    if not hasattr(val, "__call__") and not isinstance(val, BaseQuery):
                        field_name = fields_to_replace[field] if field in fields_to_replace else field
                        # is this field another SQLalchemy object, or a list of SQLalchemy objects?
                        if isinstance(val.__class__, DeclarativeMeta) or \
                                (isinstance(val, list) and len(val) > 0 and isinstance(val[0].__class__, DeclarativeMeta)):
                            # unless we're expanding this field, stop here
                            if field not in fields_to_expand:
                                # not expanding this field: set it to None and continue
                                fields[field_name] = None
                                continue
    
                        fields[field_name] = val
                # a json-encodable dict
                return fields
    
            return json.JSONEncoder.default(self, obj)
       return AlchemyEncoder
    

    Hope it helps someone!

    0 讨论(0)
  • 2020-11-22 10:22

    My take utilizing (too many?) dictionaries:

    def serialize(_query):
        #d = dictionary written to per row
        #D = dictionary d is written to each time, then reset
        #Master = dictionary of dictionaries; the id Key (int, unique from database) 
        from D is used as the Key for the dictionary D entry in Master
        Master = {}
        D = {}
        x = 0
        for u in _query:
            d = u.__dict__
            D = {}
            for n in d.keys():
               if n != '_sa_instance_state':
                        D[n] = d[n]
            x = d['id']
            Master[x] = D
        return Master
    

    Running with flask (including jsonify) and flask_sqlalchemy to print outputs as JSON.

    Call the function with jsonify(serialize()).

    Works with all SQLAlchemy queries I've tried so far (running SQLite3)

    0 讨论(0)
  • 2020-11-22 10:25

    Custom serialization and deserialization.

    "from_json" (class method) builds a Model object based on json data.

    "deserialize" could be called only on instance, and merge all data from json into Model instance.

    "serialize" - recursive serialization

    __write_only__ property is needed to define write only properties ("password_hash" for example).

    class Serializable(object):
        __exclude__ = ('id',)
        __include__ = ()
        __write_only__ = ()
    
        @classmethod
        def from_json(cls, json, selfObj=None):
            if selfObj is None:
                self = cls()
            else:
                self = selfObj
            exclude = (cls.__exclude__ or ()) + Serializable.__exclude__
            include = cls.__include__ or ()
            if json:
                for prop, value in json.iteritems():
                    # ignore all non user data, e.g. only
                    if (not (prop in exclude) | (prop in include)) and isinstance(
                            getattr(cls, prop, None), QueryableAttribute):
                        setattr(self, prop, value)
            return self
    
        def deserialize(self, json):
            if not json:
                return None
            return self.__class__.from_json(json, selfObj=self)
    
        @classmethod
        def serialize_list(cls, object_list=[]):
            output = []
            for li in object_list:
                if isinstance(li, Serializable):
                    output.append(li.serialize())
                else:
                    output.append(li)
            return output
    
        def serialize(self, **kwargs):
    
            # init write only props
            if len(getattr(self.__class__, '__write_only__', ())) == 0:
                self.__class__.__write_only__ = ()
            dictionary = {}
            expand = kwargs.get('expand', ()) or ()
            prop = 'props'
            if expand:
                # expand all the fields
                for key in expand:
                    getattr(self, key)
            iterable = self.__dict__.items()
            is_custom_property_set = False
            # include only properties passed as parameter
            if (prop in kwargs) and (kwargs.get(prop, None) is not None):
                is_custom_property_set = True
                iterable = kwargs.get(prop, None)
            # loop trough all accessible properties
            for key in iterable:
                accessor = key
                if isinstance(key, tuple):
                    accessor = key[0]
                if not (accessor in self.__class__.__write_only__) and not accessor.startswith('_'):
                    # force select from db to be able get relationships
                    if is_custom_property_set:
                        getattr(self, accessor, None)
                    if isinstance(self.__dict__.get(accessor), list):
                        dictionary[accessor] = self.__class__.serialize_list(object_list=self.__dict__.get(accessor))
                    # check if those properties are read only
                    elif isinstance(self.__dict__.get(accessor), Serializable):
                        dictionary[accessor] = self.__dict__.get(accessor).serialize()
                    else:
                        dictionary[accessor] = self.__dict__.get(accessor)
            return dictionary
    
    0 讨论(0)
  • 2020-11-22 10:27

    install simplejson by pip install simplejson and the create a class

    class Serialise(object):
    
        def _asdict(self):
            """
            Serialization logic for converting entities using flask's jsonify
    
            :return: An ordered dictionary
            :rtype: :class:`collections.OrderedDict`
            """
    
            result = OrderedDict()
            # Get the columns
            for key in self.__mapper__.c.keys():
                if isinstance(getattr(self, key), datetime):
                    result["x"] = getattr(self, key).timestamp() * 1000
                    result["timestamp"] = result["x"]
                else:
                    result[key] = getattr(self, key)
    
            return result
    

    and inherit this class to every orm classes so that this _asdict function gets registered to every ORM class and boom. And use jsonify anywhere

    0 讨论(0)
提交回复
热议问题