2

Problem

How to group fields together when serialising a flat-structured SQLAlchemy object with Marshmallow without changing the flat data structure in the background?

Example

Suppose a SQLAlchemy model in a Flask app like this:

from app import db # db using SQLAlchemy

class Data(db.Model):
  x = db.Column(db.Float(), required=True)
  y = db.Column(db.Float(), required=True)
  d = db.Column(db.Float())

How can I serialise this object so that x and y are nested into coordinates, while maintaining a flat data structure in the background (the model)? The output should look something like this:

{
  "coordinates": {
    "x": 10.56
    "y": 1
  },
  "d": 42.0
}

The problem arises specifically because I use the Data schema with the many=True option. The initialisation is roughly:

schema_data = MomentData()
schema_datas = MomentData(many=True)

Solution Candidates

So this is what I've tried so far, but none of them seemed to work.

Creating a second Schema

Adding a second schema and modifying the Data schema from before yields:

class CoordinatesSchema(Schema):
  x = fields.Float(required=True)
  y = fields.Float(required=True)

class DataSchema(Schema):
  coordinates = fields.Nested(coordinatesSchema, required=True)
  d = fields.Float()

Having that in place raises the problem of having to go through every Data item and manually adding the Coordinates schema. My data is coming from a SQLAlchemy query returning a list of Data objects, so that I can easily dump them using schema_datas.

Using fields.Dict

Since Marshmallows fields module offers a dictionary, I tried that as well.


def DataSchema(Schema):
  coordinates = fields.Dict(keys=fields.String(),
                            value=fields.Float(),
                            required=True,
                            default={
                                 "x": Data.x,
                                 "y": Data.y
                            })
  d = fields.Float()

Doesn't seem to work either, because Marshmallow can't find Data.x and Data.y automatically when using schema_datas.dump().

Using Self-Nesting

The most logical solution path would be to self-nest. But (from what I understood reading the documentation) self-nesting only refers to nesting one or more other instances within the object. I want to nest the same instance.

class DataSchema(Schema):
  x = fields.Float(required=True, load_only=True)
  y = fields.Float(required=True, load_only=True)

  coordinates = fields.Nested(
        lambda: DataSchema(only=('x', 'y')),
        dump_only=True)

But unfortunately this also didn't work.

Using Decorator @pre_dump

Inspired by this issue on Marshmallow's Github page, I tried to use the @pre_dump decorator to achieve the desired outcome, but failed again.


class CoordinatesSchema(Schema):
  x = fields.Float(required=True)
  y = fields.Float(required=True)

class DataSchema(Schema):
  coordinates = fields.Nested(coordinatesSchema, required=True)
  d = fields.Float()

  @pre_dump
  def group_coordinates(self, data, many):
    return {
      "coordinates": {
        "x": data.x,
        "y": data.y
        },
      "d": data.d
    }

But I can't figure out how to do it properly...


So my question is, what am I doing wrong and how can I solve this problem?

NE555
  • 76
  • 10
  • I don't see why the pre_dump fails. I'd rather use post_dump to do that. It's simpler to modify the dump only where needed. The pre_dump must recreate the whole object, which sucks. – Jérôme Mar 27 '20 at 15:33
  • 1
    I think the [Pluck](https://marshmallow.readthedocs.io/en/stable/api_reference.html#marshmallow.fields.Pluck) field does what you want. I don't know how it works with SQLAlchemy, though. – Jérôme Mar 27 '20 at 15:34
  • Okay thank you Jérôme! I'll change it to post_dump and try again. Oh and I think Pluck could do the trick, thank you! I'll try it tomorrow and report back, if it worked! – NE555 Mar 27 '20 at 16:36

0 Answers0