Flask-restful: marshal complex object to json

15,648

Solution 1

I found solution to that problem myself.

After playing around with flask-restful i find out that i made few mistakes:

Firstly set_marshaller should look like this:

set_marshaller = {
    'id': fields.String,
    'title': fields.String,
    'parameters': fields.Nested(parameter_marshaller)
}

Restless marshaller can handle case if parameter is list and marshals to json list.

Another problem was that in API Set parameters has lazy loading, so when i try to marshall Set i got KeyError: 'parameters', so I need explicitly load parameters like this:

class SetApi(Resource):

     @marshal_with(marshallers.set_marshaller)
     def get(self, set_id):
        entity = Set.query.get(set_id)
        entity.parameters # loads parameters from db
        return entity

Or another option is to change model relationship:

parameters = db.relationship("Parameters", backref="set", cascade="all" lazy="joined")

Solution 2

This is an addition to Zygimantas's answer:

I'm using Flask-RESTful and this is a solution to the loading of the nested properties.

You can pass a callable to the marshal decorator:

class OrgsController(Resource):
    @marshal_with(Organization.__json__())
    def get(self):
        return g.user.member.orgs

Then update the models to return the resource fields for its own entity. Nested entities will thus return the resource fields for its entity relatively.

class Organization(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    ...

    @staticmethod
    def __json__(group=None):
        _json = {
            'id': fields.String,
            'login': fields.String,
            'description': fields.String,
            'avatar_url': fields.String,
            'paid': fields.Boolean,
        }

        if group == 'flat':
            return _json

        from app.models import Repository
        _json['repos'] = fields.Nested(Repository.__json__('flat'))

        return _json

class Repository(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    owner_id = db.Column(db.Integer, db.ForeignKey('organization.id'))
    owner = db.relationship('Organization', lazy='select', backref=db.backref('repos', lazy='select'), foreign_keys=[owner_id])
    ...

    @staticmethod
    def __json__(group=None):
        _json = {
            'id': fields.String,
            'name': fields.String,
            'updated_at': fields.DateTime(dt_format='iso8601'),
        }

        if group == 'flat':
            return _json

        from app.models import Organization
        _json['owner'] = fields.Nested(Organization.__json__('flat'))

        return _json

This gives the representation I'm looking for, and honoring the lazy loading:

[
    {
        "avatar_url": "https://avatars.githubusercontent.com/u/18945?v=3",
        "description": "lorem ipsum.",
        "id": "1805",
        "login": "foobar",
        "paid": false,
        "repos":
            [
                {
                    "id": "9813",
                    "name": "barbaz",
                    "updated_at": "2014-01-23T13:51:30"
                },
                {
                    "id": "12860",
                    "name": "bazbar",
                    "updated_at": "2015-04-17T11:06:36"
                }
            ]
    }
]

I like

1) how this approach allows me to define my resource fields per entity and it is available to all my resource routes across the app.

2) how the group argument allows me to customise the representation however I desire. I only have 'flat' here, but any logic can be written and passed down to deeper nested objects.

3) entities are only loaded as necessary.

Share:
15,648

Related videos on Youtube

Zygimantas Gatelis
Author by

Zygimantas Gatelis

Updated on June 26, 2022

Comments

  • Zygimantas Gatelis
    Zygimantas Gatelis almost 2 years

    I have a question regarding flask restful extension. I'm just started to use it and faced one problem. I have flask-sqlalchemy entities that are connected many-to-one relation and I want that restful endpoint return parent entity with all its children in json using marshaller. In my case Set contains many parameters. I looked at flask-restful docs but there wasn't any explanation how to solve this case.

    Seems like I'm missing something obvious but cannot figure out any solution. Here is my code:

    # entities
    class Set(db.Model):
        id = db.Column("id", db.Integer, db.Sequence("set_id_seq"), primary_key=True)
        title = db.Column("title", db.String(256))
    
        parameters = db.relationship("Parameters", backref="set", cascade="all")
    
    
    class Parameters(db.Model):
        id = db.Column("id", db.Integer, db.Sequence("parameter_id_seq"), primary_key=True)
        flag = db.Column("flag", db.String(256))
        value = db.Column("value", db.String(256))
        set_id = db.Column("set_id", db.Integer, db.ForeignKey("set.id"))
    
    
    # marshallers
    
    from flask.ext.restful import fields
    
    parameter_marshaller = {
        "flag": fields.String,
        "value": fields.String
    }
    
    set_marshaller = {
        'id': fields.String,
        'title': fields.String,
        'parameters': fields.List(fields.Nested(parameter_marshaller))
    }
    
    # endpoint    
    
    class SetApi(Resource):
    
        @marshal_with(marshallers.set_marshaller)
        def get(self, set_id):
            entity = Set.query.get(set_id)
            return entity
    
    
    restful_api = Api(app)
    restful_api.add_resource(SetApi, "/api/set/<int:set_id>")
    

    Now when i call /api/set/1 I get server error:

    TypeError: 'Set' object is unsubscriptable

    So I need a way to correctly define set_marshaller that endpoint return this json:

    {
      "id": : "1",
      "title": "any-title",
      "parameters": [
           {"flag": "any-flag", "value": "any-value" },
           {"flag": "any-flag", "value": "any-value" },
           .....
       ]
    }
    

    I appreciate any help.

  • Atif Shafi
    Atif Shafi over 2 years
    you saved me pains