How can I artificially nest schemas in Marshmallow?

Question:

In Marshmallow, is there a way to pass the current object to a Nested field in order to produce artificially nested serializations? For example, consider this object that I’m serializing:

example = Example(
    name="Foo",
    address="301 Elm Street",
    city="Kalamazoo",
    state="MI",
)

I want to produce JSON for this that looks like this:

{
    "name": "Foo",
    "address": {
            "street": "301 Elm Street",
            "city": "Kalamazoo",
            "state": "MI"
    }
}

Essentially, this would be a nested AddressSchema inside the ExampleSchema, something like this:

class AddressSchema:
    street = fields.String(attribute="address")
    city = fields.String()
    state = fields.String()

class ExampleSchema:
    name = fields.String()
    address = fields.Nested(AddressSchema)

…but that doesn’t quite do what I’d like. I can use a custom function, but I’d like to use a built-in method if possible.

Asked By: asthasr

||

Answers:

I managed to figure out a solution that allows me to preserve introspection and use only built-in fields; it’s a little odd, though. I modified ExampleSchema to include a @pre_dump hook that adds a self-referential attribute, and pointed the field at that:

class ExampleSchema:
    name = fields.String()
    address = fields.Nested(
        AddressSchema, attribute="_marshmallow_self_reference"
    )

    @pre_dump
    def add_self_reference(self, data):
        setattr(data, "_marshmallow_self_reference", data)
Answered By: asthasr

You can define the address field as follows

address = fields.Function(lambda x: {'street': x.street, 'city': x.city, 'state': x.state})

Credit goes to this post: Nesting in Python and Marshmallow with a field that does not exist in the database

Answered By: Greg Holst

I couldn’t get the accepted answer to work. I wanted to nest some members as as a "preferences" schema inside the account.

class Color:
    id = Column(UUID)
    name = Column(Unicode(100))

class Account:
    id = Column(UUID())
    name = Column(Integer())
    age = Column(Integer())

    # These are the account's "preferences", which are stored
    # directly in the account model.
    color_ids = Column(ARRAY(UUID))
    colors = relationship("Color", primaryjoin... etc)
    opt_in = Column(Boolean())

I used marshmallow’s pre_dump decorator to call a function that sets a preferences object onto the model:

class ColorSchema(SQLAlchemySchema):
    class Meta:
        model = models.Color
        fields = [ "id", "name" ]

# Subset of Account that I want returned as a nested entry
class PreferencesSchema(SQLAlchemySchema):
    class Meta:
        model = models.Account
        fields = [ "colors", "opt_in" ]

    colors = marshmallow.fields.List(marshmallow.fields.Nested(ColorSchema))

class AccountSchema(SQLAlchemySchema):
    class Meta:
        model = models.Account
        fields = [
            "id",
            "name",
            "age",
            "preferences" # Note: does not exist on model
        ]
    preferences = fields.Nested(PreferencesSchema)

    @pre_dump
    def set_preferences(self, data, **kwargs):
        preferences = {
            "colors": data.colors,
            "opt_in": data.opt_in,
        }
        setattr(data, "preferences", preferences)
        return data

Now the response would be:

{
    "id": "uuid...",
    "name": "Joe",
    "age": 25,
    "preferences": {
        "colors": [
            { "id": "uuid...", "name": "blue" },
            { "id": "uuid...", "name": "red" },
        ],
        "opt_in": false
    }
 }
Answered By: starlabs